Skip to content

Commit 5f2b9d9

Browse files
feat(general): make logger, tracer and metrics utilities aware of provisioned concurrency (#6324)
Make utilities aware of Provisioned Concurrency
1 parent d45c913 commit 5f2b9d9

File tree

7 files changed

+115
-11
lines changed

7 files changed

+115
-11
lines changed

Diff for: aws_lambda_powertools/logging/logger.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,22 @@ def _is_cold_start() -> bool:
6464
bool
6565
cold start bool value
6666
"""
67-
cold_start = False
68-
6967
global is_cold_start
70-
if is_cold_start:
71-
cold_start = is_cold_start
68+
69+
initialization_type = os.getenv(constants.LAMBDA_INITIALIZATION_TYPE)
70+
71+
# Check for Provisioned Concurrency environment
72+
# AWS_LAMBDA_INITIALIZATION_TYPE is set when using Provisioned Concurrency
73+
if initialization_type == "provisioned-concurrency":
7274
is_cold_start = False
75+
return False
76+
77+
if not is_cold_start:
78+
return False
7379

74-
return cold_start
80+
# This is a cold start - flip the flag and return True
81+
is_cold_start = False
82+
return True
7583

7684

7785
class Logger:

Diff for: aws_lambda_powertools/metrics/provider/cold_start.py

+11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
from __future__ import annotations
22

3+
import os
4+
5+
from aws_lambda_powertools.shared import constants
6+
37
is_cold_start = True
48

9+
initialization_type = os.getenv(constants.LAMBDA_INITIALIZATION_TYPE)
10+
11+
# Check for Provisioned Concurrency environment
12+
# AWS_LAMBDA_INITIALIZATION_TYPE is set when using Provisioned Concurrency
13+
if initialization_type == "provisioned-concurrency":
14+
is_cold_start = False
15+
516

617
def reset_cold_start_flag():
718
global is_cold_start

Diff for: aws_lambda_powertools/shared/constants.py

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
SAM_LOCAL_ENV: str = "AWS_SAM_LOCAL"
5858
CHALICE_LOCAL_ENV: str = "AWS_CHALICE_CLI_MODE"
5959
LAMBDA_FUNCTION_NAME_ENV: str = "AWS_LAMBDA_FUNCTION_NAME"
60+
LAMBDA_INITIALIZATION_TYPE: str = "AWS_LAMBDA_INITIALIZATION_TYPE"
6061

6162
# Debug constants
6263
POWERTOOLS_DEV_ENV: str = "POWERTOOLS_DEV"

Diff for: aws_lambda_powertools/tracing/tracer.py

+28-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,32 @@
3030
T = TypeVar("T")
3131

3232

33+
def _is_cold_start() -> bool:
34+
"""Verifies whether is cold start
35+
36+
Returns
37+
-------
38+
bool
39+
cold start bool value
40+
"""
41+
global is_cold_start
42+
43+
initialization_type = os.getenv(constants.LAMBDA_INITIALIZATION_TYPE)
44+
45+
# Check for Provisioned Concurrency environment
46+
# AWS_LAMBDA_INITIALIZATION_TYPE is set when using Provisioned Concurrency
47+
if initialization_type == "provisioned-concurrency":
48+
is_cold_start = False
49+
return False
50+
51+
if not is_cold_start:
52+
return False
53+
54+
# This is a cold start - flip the flag and return True
55+
is_cold_start = False
56+
return True
57+
58+
3359
class Tracer:
3460
"""Tracer using AWS-XRay to provide decorators with known defaults for Lambda functions
3561
@@ -340,12 +366,9 @@ def decorate(event, context, **kwargs):
340366

341367
raise
342368
finally:
343-
global is_cold_start
369+
cold_start = _is_cold_start()
344370
logger.debug("Annotating cold start")
345-
subsegment.put_annotation(key="ColdStart", value=is_cold_start)
346-
347-
if is_cold_start:
348-
is_cold_start = False
371+
subsegment.put_annotation(key="ColdStart", value=cold_start)
349372

350373
if self.service:
351374
subsegment.put_annotation(key="Service", value=self.service)

Diff for: tests/functional/logger/required_dependencies/test_logger.py

+27
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,33 @@ def handler(event, context):
336336
assert second_log["cold_start"] is False
337337

338338

339+
def test_inject_lambda_cold_start_with_provisioned_concurrency(monkeypatch, lambda_context, stdout, service_name):
340+
341+
# GIVEN Provisioned Concurrency is enabled via AWS_LAMBDA_INITIALIZATION_TYPE environment variable
342+
# AND Logger's cold start flag is explicitly set to True (simulating fresh module import)
343+
monkeypatch.setenv("AWS_LAMBDA_INITIALIZATION_TYPE", "provisioned-concurrency")
344+
from aws_lambda_powertools.logging import logger
345+
346+
logger.is_cold_start = True
347+
348+
# GIVEN Logger is initialized
349+
logger = Logger(service=service_name, stream=stdout)
350+
351+
# WHEN a lambda function is decorated with logger, and called twice
352+
@logger.inject_lambda_context
353+
def handler(event, context):
354+
logger.info("Hello")
355+
356+
handler({}, lambda_context)
357+
handler({}, lambda_context)
358+
359+
# THEN cold_start should be False in both invocations
360+
# because Provisioned Concurrency environment variable forces cold_start to always be False
361+
first_log, second_log = capture_multiple_logging_statements_output(stdout)
362+
assert first_log["cold_start"] is False
363+
assert second_log["cold_start"] is False
364+
365+
339366
def test_logger_append_duplicated(stdout, service_name):
340367
# GIVEN Logger is initialized with request_id field
341368
logger = Logger(service=service_name, stream=stdout, request_id="value")

Diff for: tests/functional/metrics/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Metrics,
88
MetricUnit,
99
)
10-
from aws_lambda_powertools.metrics.provider.cold_start import reset_cold_start_flag
10+
from aws_lambda_powertools.metrics.base import reset_cold_start_flag
1111

1212

1313
@pytest.fixture(scope="function", autouse=True)

Diff for: tests/unit/test_tracing.py

+34
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,40 @@ def handler(event, context):
630630
assert in_subsegment_mock.put_annotation.call_args_list[2] == mocker.call(key="ColdStart", value=False)
631631

632632

633+
def test_tracer_lambda_handler_cold_start_with_provisioned_concurrency(
634+
monkeypatch,
635+
mocker,
636+
dummy_response,
637+
provider_stub,
638+
in_subsegment_mock,
639+
):
640+
# GIVEN Provisioned Concurrency is enabled via AWS_LAMBDA_INITIALIZATION_TYPE environment variable
641+
monkeypatch.setenv("AWS_LAMBDA_INITIALIZATION_TYPE", "provisioned-concurrency")
642+
# GIVEN
643+
provider = provider_stub(in_subsegment=in_subsegment_mock.in_subsegment)
644+
tracer = Tracer(provider=provider, service="booking")
645+
646+
# WHEN a Lambda handler is decorated with capture_lambda_handler
647+
# AND the handler is invoked twice consecutively
648+
@tracer.capture_lambda_handler
649+
def handler(event, context):
650+
return dummy_response
651+
652+
# First invocation
653+
handler({}, mocker.MagicMock())
654+
655+
# THEN the ColdStart annotation should be set to False for the first invocation
656+
# because Provisioned Concurrency forces cold start to be false regardless of actual state
657+
assert in_subsegment_mock.put_annotation.call_args_list[0] == mocker.call(key="ColdStart", value=False)
658+
659+
# WHEN the same handler is invoked a second time
660+
handler({}, mocker.MagicMock())
661+
662+
# THEN the ColdStart annotation should also be False for the second invocation
663+
# confirming that Provisioned Concurrency consistently overrides cold start detection
664+
assert in_subsegment_mock.put_annotation.call_args_list[2] == mocker.call(key="ColdStart", value=False)
665+
666+
633667
def test_tracer_lambda_handler_add_service_annotation(mocker, dummy_response, provider_stub, in_subsegment_mock):
634668
# GIVEN
635669
provider = provider_stub(in_subsegment=in_subsegment_mock.in_subsegment)

0 commit comments

Comments
 (0)