From 03c7807276bb82894aa9036d24e387c048996c1e Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 9 Oct 2026 14:05:01 +0200 Subject: [PATCH 1/9] . --- sentry_sdk/integrations/aws_lambda/_request.py | 8 +++----- sentry_sdk/integrations/aws_lambda/_runtime.py | 2 +- sentry_sdk/integrations/aws_lambda/consts.py | 3 +++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda/_request.py b/sentry_sdk/integrations/aws_lambda/_request.py index 320348b452..9b2e73d9f7 100644 --- a/sentry_sdk/integrations/aws_lambda/_request.py +++ b/sentry_sdk/integrations/aws_lambda/_request.py @@ -6,9 +6,7 @@ import sentry_sdk from sentry_sdk.data_collection import _apply_key_value_collection_filtering from sentry_sdk.integrations._wsgi_common import _filter_headers -from sentry_sdk.utils import ( - AnnotatedValue, -) +from sentry_sdk.utils import AnnotatedValue if TYPE_CHECKING: from typing import Any, Optional @@ -119,8 +117,8 @@ def _get_url(aws_event: "Any", aws_context: "Any") -> str: host = headers.get("Host", None) proto = headers.get("X-Forwarded-Proto", None) if proto and host and path: - return "{}://{}{}".format(proto, host, path) - return "awslambda:///{}".format(aws_context.function_name) + return f"{proto}://{host}{path}" + return f"awslambda:///{aws_context.function_name}" def _get_cloudwatch_logs_url(aws_context: "Any", start_time: "datetime") -> str: diff --git a/sentry_sdk/integrations/aws_lambda/_runtime.py b/sentry_sdk/integrations/aws_lambda/_runtime.py index 04bccb8ac8..e5af2c6d82 100644 --- a/sentry_sdk/integrations/aws_lambda/_runtime.py +++ b/sentry_sdk/integrations/aws_lambda/_runtime.py @@ -32,7 +32,7 @@ def sentry_init_error(*args: "Any", **kwargs: "Any") -> "Any": if exc_info and all(exc_info): sentry_event, hint = event_from_exception( exc_info, - mechanism={"type": "aws_lambda", "handled": False}, + mechanism={"type": IDENTIFIER, "handled": False}, ) sentry_sdk.capture_event(sentry_event, hint=hint) diff --git a/sentry_sdk/integrations/aws_lambda/consts.py b/sentry_sdk/integrations/aws_lambda/consts.py index 21d10bc026..abdf27a64c 100644 --- a/sentry_sdk/integrations/aws_lambda/consts.py +++ b/sentry_sdk/integrations/aws_lambda/consts.py @@ -8,3 +8,6 @@ # buffer time time required to send timeout warning. TIMEOUT_WARNING_BUFFER = 1500 MILLIS_TO_SECONDS = 1000.0 + +LAMBDA_METADATA_PATH = "/2026-01-15/metadata/execution-environment" +LAMBDA_METADATA_TIMEOUT = 0.5 From 416aa765988699f77045e290e8c582c4662e7879 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 9 Oct 2026 14:51:40 +0200 Subject: [PATCH 2/9] . --- .../integrations/aws_lambda/_handler.py | 95 ++++++++++++------- .../integrations/aws_lambda/_metadata.py | 45 +++++++++ sentry_sdk/integrations/aws_lambda/consts.py | 5 - .../index.py | 8 ++ .../aws_lambda/test_aws_lambda.py | 30 +++++- tests/integrations/aws_lambda/utils.py | 53 +++++++++-- 6 files changed, 188 insertions(+), 48 deletions(-) create mode 100644 sentry_sdk/integrations/aws_lambda/_metadata.py diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index ecc8d43f76..ccc7ef13d2 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -1,10 +1,11 @@ import functools import sys +from os import environ from typing import TYPE_CHECKING from urllib.parse import urlencode import sentry_sdk -from sentry_sdk.consts import OP +from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.data_collection import _apply_key_value_collection_filtering from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.aws_lambda._request import ( @@ -12,13 +13,11 @@ _make_request_event_processor, ) from sentry_sdk.integrations.aws_lambda.consts import ( - MILLIS_TO_SECONDS, - ORIGIN, - TIMEOUT_WARNING_BUFFER, -) -from sentry_sdk.integrations.cloud_resource_context import ( CLOUD_PLATFORM, CLOUD_PROVIDER, + IDENTIFIER, + LATEST_FUNCTION_VERSION, + ORIGIN, ) from sentry_sdk.scope import Scope from sentry_sdk.traces import SegmentNameSource @@ -30,10 +29,15 @@ ) if TYPE_CHECKING: - from typing import Any, Callable, TypeVar + from typing import Any, Callable, Mapping, TypeVar + + from sentry_sdk._types import Attributes F = TypeVar("F", bound=Callable[..., Any]) +TIMEOUT_WARNING_BUFFER = 1500 +MILLIS_TO_SECONDS = 1000.0 + def _wrap_handler(handler: "F") -> "F": from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration @@ -72,8 +76,6 @@ def sentry_handler( request_data = {} configured_time = aws_context.get_remaining_time_in_millis() - aws_region = aws_context.invoked_function_arn.split(":")[3] - with sentry_sdk.isolation_scope() as scope: timeout_thread = None with capture_internal_exceptions(): @@ -111,14 +113,18 @@ def sentry_handler( headers = {} header_attributes: "dict[str, Any]" = {} - for header, header_value in _filter_headers(headers).items(): - header_attributes[f"http.request.header.{header.lower()}"] = [ - header_value - ] + filtered_headers: "Mapping[str, Any]" = {} + with capture_internal_exceptions(): + filtered_headers = _filter_headers(headers) + for header, header_value in filtered_headers.items(): + with capture_internal_exceptions(): + header_attributes[ + f"{SPANDATA.HTTP_REQUEST_HEADER}.{header.lower()}" + ] = [header_value] additional_attributes: "dict[str, Any]" = {} if "httpMethod" in request_data: - additional_attributes["http.request.method"] = request_data[ + additional_attributes[SPANDATA.HTTP_REQUEST_METHOD] = request_data[ "httpMethod" ] @@ -131,7 +137,9 @@ def sentry_handler( behaviour=data_collection["url_query_params"], ) if filtered_qs: - additional_attributes["url.query"] = urlencode(filtered_qs) + additional_attributes[SPANDATA.URL_QUERY] = urlencode( + filtered_qs + ) if not scope._user and data_collection["user_info"]: user_info = _get_user_from_event(request_data) @@ -144,30 +152,49 @@ def sentry_handler( } function_name = aws_context.function_name + invoked_function_arn = aws_context.invoked_function_arn + function_version = aws_context.function_version + + attributes: "Attributes" = { + SPANDATA.SENTRY_OP: OP.FUNCTION_AWS, + SPANDATA.SENTRY_ORIGIN: ORIGIN, + SPANDATA.SENTRY_KIND: "server", + SPANDATA.SENTRY_SEGMENT_NAME_SOURCE: SegmentNameSource.COMPONENT, + SPANDATA.CLOUD_PROVIDER: CLOUD_PROVIDER, + SPANDATA.CLOUD_PLATFORM: CLOUD_PLATFORM, + SPANDATA.CLOUD_REGION: environ["AWS_REGION"], + SPANDATA.FAAS_NAME: environ["AWS_LAMBDA_FUNCTION_NAME"], + SPANDATA.FAAS_VERSION: environ["AWS_LAMBDA_FUNCTION_VERSION"], + SPANDATA.FAAS_INVOCATION_ID: aws_context.aws_request_id, + SPANDATA.AWS_LAMBDA_INVOKED_ARN: invoked_function_arn, + SPANDATA.AWS_LOG_GROUP_NAMES: [aws_context.log_group_name], + SPANDATA.AWS_LOG_STREAM_NAMES: [aws_context.log_stream_name], + SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT: batch_size, + **header_attributes, + **additional_attributes, + } + + arn_parts = invoked_function_arn.split(":") + with capture_internal_exceptions(): + attributes[SPANDATA.CLOUD_ACCOUNT_ID] = arn_parts[4] + + resource_id = ":".join(arn_parts[:7]) + if function_version != LATEST_FUNCTION_VERSION: + resource_id = f"{resource_id}:{function_version}" + attributes[SPANDATA.CLOUD_RESOURCE_ID] = resource_id + + if "AWS_LAMBDA_METADATA_API" in environ: + with capture_internal_exceptions(): + availability_zone = _get_availability_zone() + if availability_zone is not None: + attributes[SPANDATA.CLOUD_AVAILABILITY_ZONE] = availability_zone sentry_sdk.continue_trace(headers) Scope.set_custom_sampling_context(sampling_context) with sentry_sdk.start_span( name=function_name, parent_span=None, - attributes={ - "sentry.op": OP.FUNCTION_AWS, - "sentry.origin": ORIGIN, - "sentry.segment.name.source": SegmentNameSource.COMPONENT, - "cloud.region": aws_region, - "cloud.resource_id": aws_context.invoked_function_arn, - "cloud.platform": CLOUD_PLATFORM.AWS_LAMBDA, - "cloud.provider": CLOUD_PROVIDER.AWS, - "faas.name": function_name, - "faas.invocation_id": aws_context.aws_request_id, - "faas.version": aws_context.function_version, - "aws.lambda.invoked_arn": aws_context.invoked_function_arn, - "aws.log.group.names": [aws_context.log_group_name], - "aws.log.stream.names": [aws_context.log_stream_name], - "messaging.batch.message_count": batch_size, - **header_attributes, - **additional_attributes, - }, + attributes=attributes, ): try: return handler(aws_event, aws_context, *args, **kwargs) @@ -175,7 +202,7 @@ def sentry_handler( exc_info = sys.exc_info() sentry_event, hint = event_from_exception( exc_info, - mechanism={"type": "aws_lambda", "handled": False}, + mechanism={"type": IDENTIFIER, "handled": False}, ) sentry_sdk.capture_event(sentry_event, hint=hint) reraise(*exc_info) diff --git a/sentry_sdk/integrations/aws_lambda/_metadata.py b/sentry_sdk/integrations/aws_lambda/_metadata.py new file mode 100644 index 0000000000..bfda65d771 --- /dev/null +++ b/sentry_sdk/integrations/aws_lambda/_metadata.py @@ -0,0 +1,45 @@ +import json +import time +from os import environ +from typing import TYPE_CHECKING + +import urllib3 + +from sentry_sdk.integrations.aws_lambda.consts import ( + LAMBDA_METADATA_PATH, +) + +if TYPE_CHECKING: + from typing import Optional, Tuple + +_lambda_metadata_http = urllib3.PoolManager( + timeout=urllib3.Timeout(connect=0.5, read=0.5), + retries=False, +) +# (`expires_at`, `AvailabilityZoneID`), e.g. (1717987200.0, "use1-az1") +_lambda_metadata_cache: "Optional[Tuple[float, str]]" = None + + +def _get_availability_zone() -> "Optional[str]": + now = time.monotonic() + global _lambda_metadata_cache + if _lambda_metadata_cache is not None and _lambda_metadata_cache[0] > now: + return _lambda_metadata_cache[1] + + # bearer token is required to prevent SSRF. + response = _lambda_metadata_http.request( + "GET", + f"http://{environ['AWS_LAMBDA_METADATA_API']}{LAMBDA_METADATA_PATH}", + headers={"Authorization": f"Bearer {environ['AWS_LAMBDA_METADATA_TOKEN']}"}, + ) + if response.status != 200: + return None + + availability_zone = json.loads(response.data.decode("utf-8"))["AvailabilityZoneID"] + + # AWS shortens `max-age` during SnapStart initialization so restored + # environments refresh `AvailabilityZoneID` for their new AZ. + # https://docs.aws.amazon.com/lambda/latest/dg/configuration-metadata-endpoint.html + max_age = response.headers["Cache-Control"].split("max-age=", 1)[1].split(",", 1)[0] + _lambda_metadata_cache = (now + float(max_age), availability_zone) + return availability_zone diff --git a/sentry_sdk/integrations/aws_lambda/consts.py b/sentry_sdk/integrations/aws_lambda/consts.py index abdf27a64c..cddb7ee5fa 100644 --- a/sentry_sdk/integrations/aws_lambda/consts.py +++ b/sentry_sdk/integrations/aws_lambda/consts.py @@ -5,9 +5,4 @@ CLOUD_PLATFORM = "aws_lambda" LATEST_FUNCTION_VERSION = "$LATEST" -# buffer time time required to send timeout warning. -TIMEOUT_WARNING_BUFFER = 1500 -MILLIS_TO_SECONDS = 1000.0 - LAMBDA_METADATA_PATH = "/2026-01-15/metadata/execution-environment" -LAMBDA_METADATA_TIMEOUT = 0.5 diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py index b24dec6e95..a266ab02f7 100644 --- a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py @@ -3,6 +3,14 @@ import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration +# otherwise SAM will call its built-in metadata endpoint/token. +os.environ["AWS_LAMBDA_METADATA_API"] = os.environ[ + "SENTRY_TEST_AWS_LAMBDA_METADATA_API" +] +os.environ["AWS_LAMBDA_METADATA_TOKEN"] = os.environ[ + "SENTRY_TEST_AWS_LAMBDA_METADATA_TOKEN" +] + sentry_sdk.init( dsn=os.environ.get("SENTRY_DSN"), traces_sample_rate=1.0, diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index dea8cd965d..ec8ba9f782 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -730,12 +730,19 @@ def test_no_error(lambda_client, test_environment): assert _get_span_attr(attrs, SPANDATA.SENTRY_OP) == OP.FUNCTION_AWS assert _get_span_attr(attrs, SPANDATA.SENTRY_ORIGIN) == ORIGIN + assert _get_span_attr(attrs, SPANDATA.SENTRY_KIND) == "server" assert ( _get_span_attr(attrs, SPANDATA.SENTRY_SEGMENT_NAME_SOURCE) == SegmentNameSource.COMPONENT ) assert _get_span_attr(attrs, SPANDATA.CLOUD_PROVIDER) == CLOUD_PROVIDER assert _get_span_attr(attrs, SPANDATA.CLOUD_PLATFORM) == CLOUD_PLATFORM + assert _get_span_attr(attrs, SPANDATA.CLOUD_ACCOUNT_ID) == "012345678912" + assert _get_span_attr(attrs, SPANDATA.CLOUD_REGION) == "us-east-1" + assert ( + _get_span_attr(attrs, SPANDATA.CLOUD_RESOURCE_ID) + == "arn:aws:lambda:us-east-1:012345678912:function:BasicOkSpanStreaming" + ) assert _get_span_attr(attrs, SPANDATA.FAAS_NAME) == "BasicOkSpanStreaming" assert _get_span_attr(attrs, SPANDATA.FAAS_VERSION) == LATEST_FUNCTION_VERSION assert SPANDATA.FAAS_INVOCATION_ID in attrs @@ -894,6 +901,7 @@ def test_request_attributes(lambda_client, test_environment): def test_url_query_params_with_data_collection(lambda_client, test_environment): + server = test_environment["server"] payload = { "httpMethod": "GET", "queryStringParameters": { @@ -908,7 +916,7 @@ def test_url_query_params_with_data_collection(lambda_client, test_environment): FunctionName="BasicOkSpanStreamingDataCollection", Payload=json.dumps(payload), ) - span_items = test_environment["server"].span_items + span_items = server.span_items segment_spans = [s for s in span_items if s["is_segment"]] assert len(segment_spans) == 1 @@ -921,6 +929,26 @@ def test_url_query_params_with_data_collection(lambda_client, test_environment): _get_span_attr(attrs, SPANDATA.URL_QUERY) == "page=2&tracking=%5BFiltered%5D&token=%5BFiltered%5D" ) + assert _get_span_attr(attrs, SPANDATA.CLOUD_AVAILABILITY_ZONE) == "use1-az1" + # The container may already be warm (and the metadata cached) from an earlier + # test invoking the same function, so only assert that the second invocation + # is served from the cache. + metadata_requests = server.lambda_metadata_requests + assert metadata_requests >= 1 + + server.clear_envelopes() + lambda_client.invoke( + FunctionName="BasicOkSpanStreamingDataCollection", + Payload=json.dumps(payload), + ) + segment_spans = [s for s in server.span_items if s["is_segment"]] + + assert len(segment_spans) == 1 + assert ( + _get_span_attr(segment_spans[0]["attributes"], SPANDATA.CLOUD_AVAILABILITY_ZONE) + == "use1-az1" + ) + assert server.lambda_metadata_requests == metadata_requests def test_user_info_with_data_collection_user_info_off(lambda_client, test_environment): diff --git a/tests/integrations/aws_lambda/utils.py b/tests/integrations/aws_lambda/utils.py index 8933d856ed..0af7535a96 100644 --- a/tests/integrations/aws_lambda/utils.py +++ b/tests/integrations/aws_lambda/utils.py @@ -17,14 +17,17 @@ ) from constructs import Construct from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse from scripts.build_aws_lambda_layer import DIST_PATH, build_packaged_zip +from sentry_sdk.integrations.aws_lambda.consts import LAMBDA_METADATA_PATH LAMBDA_FUNCTION_DIR = "./tests/integrations/aws_lambda/lambda_functions/" LAMBDA_FUNCTION_WITH_EMBEDDED_SDK_DIR = ( "./tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/" ) LAMBDA_FUNCTION_TIMEOUT = 10 +LAMBDA_METADATA_TOKEN = "test-token" SAM_PORT = 3001 PYTHON_VERSION = f"python{sys.version_info.major}.{sys.version_info.minor}" @@ -145,12 +148,11 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: sdk_path = os.path.join( LAMBDA_FUNCTION_WITH_EMBEDDED_SDK_DIR, lambda_dir, "sentry_sdk" ) - if not os.path.exists(sdk_path): - # Find the Sentry SDK in the current environment - import sentry_sdk as sdk_module + # refresh the embedded SDK so repeated runs cannot use stale source files. + import sentry_sdk as sdk_module - sdk_source = os.path.dirname(sdk_module.__file__) - shutil.copytree(sdk_source, sdk_path) + sdk_source = os.path.dirname(sdk_module.__file__) + shutil.copytree(sdk_source, sdk_path, dirs_exist_ok=True) # Install the requirements of Sentry SDK into the function directory subprocess.check_call( @@ -166,6 +168,22 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: ] ) + environment = {"SENTRY_DSN": dsn} + if lambda_dir == "BasicOkSpanStreamingDataCollection": + # AWS exposes these env. vars when the metadata endpoint + # (`2026-01-15/metadata/execution-environment`) is available. + # We set et them only on the function that exercises `cloud.availability_zone`. + environment.update( + { + "SENTRY_TEST_AWS_LAMBDA_METADATA_API": ( + f"{get_host_ip()}:9999" + ), + "SENTRY_TEST_AWS_LAMBDA_METADATA_TOKEN": ( + LAMBDA_METADATA_TOKEN + ), + } + ) + CfnResource( self, lambda_dir, @@ -179,9 +197,7 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: "Timeout": LAMBDA_FUNCTION_TIMEOUT, "Architectures": [ARCHITECTURE], "Environment": { - "Variables": { - "SENTRY_DSN": dsn, - } + "Variables": environment, }, }, ) @@ -225,6 +241,7 @@ class SentryServerForTesting: def __init__(self, host="0.0.0.0", port=9999, log_level="warning"): self.envelopes = [] self.span_items = [] + self.lambda_metadata_requests = 0 self.host = host self.port = port self.log_level = log_level @@ -272,6 +289,26 @@ async def envelope(request: Request): return {"status": "ok"} + @self.app.get(LAMBDA_METADATA_PATH) + async def lambda_metadata(request: Request): + """ + Mirrors the Lambda execution-environment metadata endpoint + (`2026-01-15/metadata/execution-environment`) used to collect + `AvailabilityZoneID`, including its cache lifetime. + https://docs.aws.amazon.com/lambda/latest/dg/configuration-metadata-endpoint.html + """ + self.lambda_metadata_requests += 1 + + if request.headers.get("Authorization") != ( + f"Bearer {LAMBDA_METADATA_TOKEN}" + ): + return JSONResponse({"message": "Unauthorized"}, status_code=401) + + return JSONResponse( + {"AvailabilityZoneID": "use1-az1"}, + headers={"Cache-Control": "private, max-age=43200, immutable"}, + ) + def run_server(self): uvicorn.run(self.app, host=self.host, port=self.port, log_level=self.log_level) From 5ede22496bbb5b578d1d88033ab1a47386b8194b Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 9 Oct 2026 15:30:53 +0200 Subject: [PATCH 3/9] add `has_span_streaming_enabled(client.options)` conditional --- sentry_sdk/integrations/aws_lambda/_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index ccc7ef13d2..3357a1e91e 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -183,7 +183,7 @@ def sentry_handler( resource_id = f"{resource_id}:{function_version}" attributes[SPANDATA.CLOUD_RESOURCE_ID] = resource_id - if "AWS_LAMBDA_METADATA_API" in environ: + if "AWS_LAMBDA_METADATA_API" in environ and has_span_streaming_enabled(client.options): with capture_internal_exceptions(): availability_zone = _get_availability_zone() if availability_zone is not None: From 857ef30fc6ae17fce31392e4d3ddf3920cb73f08 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 9 Oct 2026 16:16:01 +0200 Subject: [PATCH 4/9] lint --- sentry_sdk/integrations/aws_lambda/_handler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index 3357a1e91e..e6e1ccdf69 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -183,7 +183,9 @@ def sentry_handler( resource_id = f"{resource_id}:{function_version}" attributes[SPANDATA.CLOUD_RESOURCE_ID] = resource_id - if "AWS_LAMBDA_METADATA_API" in environ and has_span_streaming_enabled(client.options): + if "AWS_LAMBDA_METADATA_API" in environ and has_span_streaming_enabled( + client.options + ): with capture_internal_exceptions(): availability_zone = _get_availability_zone() if availability_zone is not None: From a62c55df1ff556378550b6237abef70acd8fec16 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 9 Oct 2026 16:26:42 +0200 Subject: [PATCH 5/9] . --- .../integrations/aws_lambda/_handler.py | 40 ++++++++++--------- .../integrations/aws_lambda/_metadata.py | 3 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index e6e1ccdf69..b826426fb8 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -152,8 +152,6 @@ def sentry_handler( } function_name = aws_context.function_name - invoked_function_arn = aws_context.invoked_function_arn - function_version = aws_context.function_version attributes: "Attributes" = { SPANDATA.SENTRY_OP: OP.FUNCTION_AWS, @@ -162,30 +160,34 @@ def sentry_handler( SPANDATA.SENTRY_SEGMENT_NAME_SOURCE: SegmentNameSource.COMPONENT, SPANDATA.CLOUD_PROVIDER: CLOUD_PROVIDER, SPANDATA.CLOUD_PLATFORM: CLOUD_PLATFORM, - SPANDATA.CLOUD_REGION: environ["AWS_REGION"], - SPANDATA.FAAS_NAME: environ["AWS_LAMBDA_FUNCTION_NAME"], - SPANDATA.FAAS_VERSION: environ["AWS_LAMBDA_FUNCTION_VERSION"], - SPANDATA.FAAS_INVOCATION_ID: aws_context.aws_request_id, - SPANDATA.AWS_LAMBDA_INVOKED_ARN: invoked_function_arn, - SPANDATA.AWS_LOG_GROUP_NAMES: [aws_context.log_group_name], - SPANDATA.AWS_LOG_STREAM_NAMES: [aws_context.log_stream_name], SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT: batch_size, **header_attributes, **additional_attributes, } - arn_parts = invoked_function_arn.split(":") with capture_internal_exceptions(): - attributes[SPANDATA.CLOUD_ACCOUNT_ID] = arn_parts[4] - - resource_id = ":".join(arn_parts[:7]) - if function_version != LATEST_FUNCTION_VERSION: - resource_id = f"{resource_id}:{function_version}" - attributes[SPANDATA.CLOUD_RESOURCE_ID] = resource_id + invoked_function_arn = aws_context.invoked_function_arn + function_version = aws_context.function_version + arn_parts = invoked_function_arn.split(":") + resource_id = ":".join(arn_parts[:7]) + if function_version != LATEST_FUNCTION_VERSION: + resource_id = f"{resource_id}:{function_version}" + + attributes.update( + { + SPANDATA.CLOUD_REGION: environ["AWS_REGION"], + SPANDATA.CLOUD_ACCOUNT_ID: arn_parts[4], + SPANDATA.CLOUD_RESOURCE_ID: resource_id, + SPANDATA.FAAS_NAME: environ["AWS_LAMBDA_FUNCTION_NAME"], + SPANDATA.FAAS_VERSION: environ["AWS_LAMBDA_FUNCTION_VERSION"], + SPANDATA.FAAS_INVOCATION_ID: aws_context.aws_request_id, + SPANDATA.AWS_LAMBDA_INVOKED_ARN: invoked_function_arn, + SPANDATA.AWS_LOG_GROUP_NAMES: [aws_context.log_group_name], + SPANDATA.AWS_LOG_STREAM_NAMES: [aws_context.log_stream_name], + } + ) - if "AWS_LAMBDA_METADATA_API" in environ and has_span_streaming_enabled( - client.options - ): + if "AWS_LAMBDA_METADATA_API" in environ: with capture_internal_exceptions(): availability_zone = _get_availability_zone() if availability_zone is not None: diff --git a/sentry_sdk/integrations/aws_lambda/_metadata.py b/sentry_sdk/integrations/aws_lambda/_metadata.py index bfda65d771..7875966cab 100644 --- a/sentry_sdk/integrations/aws_lambda/_metadata.py +++ b/sentry_sdk/integrations/aws_lambda/_metadata.py @@ -6,6 +6,7 @@ import urllib3 from sentry_sdk.integrations.aws_lambda.consts import ( + LAMBDA_METADATA_FAILURE_CACHE_SECONDS, LAMBDA_METADATA_PATH, ) @@ -21,7 +22,7 @@ def _get_availability_zone() -> "Optional[str]": - now = time.monotonic() + now = time.time() global _lambda_metadata_cache if _lambda_metadata_cache is not None and _lambda_metadata_cache[0] > now: return _lambda_metadata_cache[1] From 5932767bd4a497e7892b4a001c126218544312e9 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 9 Oct 2026 16:28:02 +0200 Subject: [PATCH 6/9] . --- sentry_sdk/integrations/aws_lambda/_metadata.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/integrations/aws_lambda/_metadata.py b/sentry_sdk/integrations/aws_lambda/_metadata.py index 7875966cab..eab7b49a13 100644 --- a/sentry_sdk/integrations/aws_lambda/_metadata.py +++ b/sentry_sdk/integrations/aws_lambda/_metadata.py @@ -6,7 +6,6 @@ import urllib3 from sentry_sdk.integrations.aws_lambda.consts import ( - LAMBDA_METADATA_FAILURE_CACHE_SECONDS, LAMBDA_METADATA_PATH, ) From 1ba4dcfd7864979d55dc8aa526d929fa68eb77df Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 16 Oct 2026 10:02:39 +0200 Subject: [PATCH 7/9] use vars --- sentry_sdk/integrations/aws_lambda/_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index b826426fb8..73d081b39a 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -178,8 +178,8 @@ def sentry_handler( SPANDATA.CLOUD_REGION: environ["AWS_REGION"], SPANDATA.CLOUD_ACCOUNT_ID: arn_parts[4], SPANDATA.CLOUD_RESOURCE_ID: resource_id, - SPANDATA.FAAS_NAME: environ["AWS_LAMBDA_FUNCTION_NAME"], - SPANDATA.FAAS_VERSION: environ["AWS_LAMBDA_FUNCTION_VERSION"], + SPANDATA.FAAS_NAME: function_name, + SPANDATA.FAAS_VERSION: function_version, SPANDATA.FAAS_INVOCATION_ID: aws_context.aws_request_id, SPANDATA.AWS_LAMBDA_INVOKED_ARN: invoked_function_arn, SPANDATA.AWS_LOG_GROUP_NAMES: [aws_context.log_group_name], From 73de8abc60b3c10569e727e02a4f49fa6b3a379f Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 16 Oct 2026 11:24:53 +0200 Subject: [PATCH 8/9] add version tests --- .../index.py | 4 ++++ .../RaiseErrorSpanStreaming/index.py | 5 +++++ .../aws_lambda/test_aws_lambda.py | 20 +++++++++++++++---- tests/integrations/aws_lambda/utils.py | 17 ++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py index a266ab02f7..9105aab073 100644 --- a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py @@ -10,6 +10,10 @@ os.environ["AWS_LAMBDA_METADATA_TOKEN"] = os.environ[ "SENTRY_TEST_AWS_LAMBDA_METADATA_TOKEN" ] +# the alias is only in the name to get a qualified invoked ARN. +os.environ["AWS_LAMBDA_FUNCTION_NAME"] = os.environ["AWS_LAMBDA_FUNCTION_NAME"].split( + ":" +)[0] sentry_sdk.init( dsn=os.environ.get("SENTRY_DSN"), diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorSpanStreaming/index.py b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorSpanStreaming/index.py index 9503f04966..f2a63f2749 100644 --- a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorSpanStreaming/index.py +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorSpanStreaming/index.py @@ -3,6 +3,11 @@ import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration +# the alias is only in the name to get a qualified invoked ARN +os.environ["AWS_LAMBDA_FUNCTION_NAME"] = os.environ["AWS_LAMBDA_FUNCTION_NAME"].split( + ":" +)[0] + sentry_sdk.init( dsn=os.environ.get("SENTRY_DSN"), traces_sample_rate=1.0, diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index ec8ba9f782..15bc128d5d 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -794,11 +794,15 @@ def test_error(lambda_client, test_environment): assert _get_span_attr(attrs, SPANDATA.CLOUD_PROVIDER) == CLOUD_PROVIDER assert _get_span_attr(attrs, SPANDATA.CLOUD_PLATFORM) == CLOUD_PLATFORM assert _get_span_attr(attrs, SPANDATA.FAAS_NAME) == "RaiseErrorSpanStreaming" - assert _get_span_attr(attrs, SPANDATA.FAAS_VERSION) == LATEST_FUNCTION_VERSION + assert ( + _get_span_attr(attrs, SPANDATA.CLOUD_RESOURCE_ID) + == "arn:aws:lambda:us-east-1:012345678912:function:RaiseErrorSpanStreaming:5" + ) + assert _get_span_attr(attrs, SPANDATA.FAAS_VERSION) == "5" assert SPANDATA.FAAS_INVOCATION_ID in attrs assert ( _get_span_attr(attrs, SPANDATA.AWS_LAMBDA_INVOKED_ARN) - == "arn:aws:lambda:us-east-1:012345678912:function:RaiseErrorSpanStreaming" + == "arn:aws:lambda:us-east-1:012345678912:function:RaiseErrorSpanStreaming:5" ) assert _get_span_attr(attrs, SPANDATA.AWS_LOG_GROUP_NAMES) == [ "aws/lambda/RaiseErrorSpanStreaming" @@ -847,7 +851,7 @@ def test_trace_continuation(lambda_client, test_environment): assert _get_span_attr(attrs, SPANDATA.CLOUD_PROVIDER) == CLOUD_PROVIDER assert _get_span_attr(attrs, SPANDATA.CLOUD_PLATFORM) == CLOUD_PLATFORM assert _get_span_attr(attrs, SPANDATA.FAAS_NAME) == "RaiseErrorSpanStreaming" - assert _get_span_attr(attrs, SPANDATA.FAAS_VERSION) == LATEST_FUNCTION_VERSION + assert _get_span_attr(attrs, SPANDATA.FAAS_VERSION) == "5" assert SPANDATA.FAAS_INVOCATION_ID in attrs @@ -890,7 +894,15 @@ def test_request_attributes(lambda_client, test_environment): ) assert _get_span_attr(attrs, SPANDATA.CLOUD_PROVIDER) == CLOUD_PROVIDER assert _get_span_attr(attrs, SPANDATA.CLOUD_PLATFORM) == CLOUD_PLATFORM - assert _get_span_attr(attrs, SPANDATA.FAAS_VERSION) == LATEST_FUNCTION_VERSION + assert ( + _get_span_attr(attrs, SPANDATA.AWS_LAMBDA_INVOKED_ARN) + == "arn:aws:lambda:us-east-1:012345678912:function:BasicOkSpanStreamingDataCollection:prod" + ) + assert ( + _get_span_attr(attrs, SPANDATA.CLOUD_RESOURCE_ID) + == "arn:aws:lambda:us-east-1:012345678912:function:BasicOkSpanStreamingDataCollection:5" + ) + assert _get_span_attr(attrs, SPANDATA.FAAS_VERSION) == "5" assert SPANDATA.FAAS_INVOCATION_ID in attrs assert _get_span_attr(attrs, SPANDATA.AWS_LOG_GROUP_NAMES) == [ "aws/lambda/BasicOkSpanStreamingDataCollection" diff --git a/tests/integrations/aws_lambda/utils.py b/tests/integrations/aws_lambda/utils.py index 0af7535a96..025b4a3ca6 100644 --- a/tests/integrations/aws_lambda/utils.py +++ b/tests/integrations/aws_lambda/utils.py @@ -30,6 +30,13 @@ LAMBDA_METADATA_TOKEN = "test-token" SAM_PORT = 3001 +# functions invoked through a published version or an alias instead of `$LATEST`: +# function name -> (qualifier of the invoked ARN, version the qualifier resolves to) +QUALIFIED_FUNCTIONS = { + "RaiseErrorSpanStreaming": ("5", "5"), + "BasicOkSpanStreamingDataCollection": ("prod", "5"), +} + PYTHON_VERSION = f"python{sys.version_info.major}.{sys.version_info.minor}" # Match the host CPU architecture so local runs on ARM machines (e.g. macOS) @@ -183,6 +190,16 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: ), } ) + if lambda_dir in QUALIFIED_FUNCTIONS: + qualifier, function_version = QUALIFIED_FUNCTIONS[lambda_dir] + # SAM builds the invoked ARN from `AWS_LAMBDA_FUNCTION_NAME`, + # so the qualifier is appended there; the function's `index.py` strips it again. + environment.update( + { + "AWS_LAMBDA_FUNCTION_NAME": f"{lambda_dir}:{qualifier}", + "AWS_LAMBDA_FUNCTION_VERSION": function_version, + } + ) CfnResource( self, From 295d5c32e5a7d8a49819491737feb5dfb18253c6 Mon Sep 17 00:00:00 2001 From: Pablo Deputter Date: Fri, 16 Oct 2026 14:52:12 +0200 Subject: [PATCH 9/9] remove everything related to AZ --- sentry_sdk/consts.py | 6 --- .../integrations/aws_lambda/_handler.py | 6 --- .../integrations/aws_lambda/_metadata.py | 45 ------------------- sentry_sdk/integrations/aws_lambda/consts.py | 2 - .../index.py | 7 --- .../aws_lambda/test_aws_lambda.py | 20 --------- tests/integrations/aws_lambda/utils.py | 38 ---------------- 7 files changed, 124 deletions(-) delete mode 100644 sentry_sdk/integrations/aws_lambda/_metadata.py diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 5ff880ab73..ee606f6d2c 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -453,12 +453,6 @@ class SPANDATA: Example: "123456789012" """ - CLOUD_AVAILABILITY_ZONE = "cloud.availability_zone" - """ - Cloud regions often have multiple, isolated locations known as zones to increase availability - Example: "us-east-1c" - """ - CLOUD_PLATFORM = "cloud.platform" """ The cloud platform in use. diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index 73d081b39a..89ba0089b7 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -187,12 +187,6 @@ def sentry_handler( } ) - if "AWS_LAMBDA_METADATA_API" in environ: - with capture_internal_exceptions(): - availability_zone = _get_availability_zone() - if availability_zone is not None: - attributes[SPANDATA.CLOUD_AVAILABILITY_ZONE] = availability_zone - sentry_sdk.continue_trace(headers) Scope.set_custom_sampling_context(sampling_context) with sentry_sdk.start_span( diff --git a/sentry_sdk/integrations/aws_lambda/_metadata.py b/sentry_sdk/integrations/aws_lambda/_metadata.py deleted file mode 100644 index eab7b49a13..0000000000 --- a/sentry_sdk/integrations/aws_lambda/_metadata.py +++ /dev/null @@ -1,45 +0,0 @@ -import json -import time -from os import environ -from typing import TYPE_CHECKING - -import urllib3 - -from sentry_sdk.integrations.aws_lambda.consts import ( - LAMBDA_METADATA_PATH, -) - -if TYPE_CHECKING: - from typing import Optional, Tuple - -_lambda_metadata_http = urllib3.PoolManager( - timeout=urllib3.Timeout(connect=0.5, read=0.5), - retries=False, -) -# (`expires_at`, `AvailabilityZoneID`), e.g. (1717987200.0, "use1-az1") -_lambda_metadata_cache: "Optional[Tuple[float, str]]" = None - - -def _get_availability_zone() -> "Optional[str]": - now = time.time() - global _lambda_metadata_cache - if _lambda_metadata_cache is not None and _lambda_metadata_cache[0] > now: - return _lambda_metadata_cache[1] - - # bearer token is required to prevent SSRF. - response = _lambda_metadata_http.request( - "GET", - f"http://{environ['AWS_LAMBDA_METADATA_API']}{LAMBDA_METADATA_PATH}", - headers={"Authorization": f"Bearer {environ['AWS_LAMBDA_METADATA_TOKEN']}"}, - ) - if response.status != 200: - return None - - availability_zone = json.loads(response.data.decode("utf-8"))["AvailabilityZoneID"] - - # AWS shortens `max-age` during SnapStart initialization so restored - # environments refresh `AvailabilityZoneID` for their new AZ. - # https://docs.aws.amazon.com/lambda/latest/dg/configuration-metadata-endpoint.html - max_age = response.headers["Cache-Control"].split("max-age=", 1)[1].split(",", 1)[0] - _lambda_metadata_cache = (now + float(max_age), availability_zone) - return availability_zone diff --git a/sentry_sdk/integrations/aws_lambda/consts.py b/sentry_sdk/integrations/aws_lambda/consts.py index cddb7ee5fa..783534f975 100644 --- a/sentry_sdk/integrations/aws_lambda/consts.py +++ b/sentry_sdk/integrations/aws_lambda/consts.py @@ -4,5 +4,3 @@ CLOUD_PROVIDER = "aws" CLOUD_PLATFORM = "aws_lambda" LATEST_FUNCTION_VERSION = "$LATEST" - -LAMBDA_METADATA_PATH = "/2026-01-15/metadata/execution-environment" diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py index 9105aab073..73ac2a5cc2 100644 --- a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/BasicOkSpanStreamingDataCollection/index.py @@ -3,13 +3,6 @@ import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration -# otherwise SAM will call its built-in metadata endpoint/token. -os.environ["AWS_LAMBDA_METADATA_API"] = os.environ[ - "SENTRY_TEST_AWS_LAMBDA_METADATA_API" -] -os.environ["AWS_LAMBDA_METADATA_TOKEN"] = os.environ[ - "SENTRY_TEST_AWS_LAMBDA_METADATA_TOKEN" -] # the alias is only in the name to get a qualified invoked ARN. os.environ["AWS_LAMBDA_FUNCTION_NAME"] = os.environ["AWS_LAMBDA_FUNCTION_NAME"].split( ":" diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index 15bc128d5d..2d7712b2d2 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -941,26 +941,6 @@ def test_url_query_params_with_data_collection(lambda_client, test_environment): _get_span_attr(attrs, SPANDATA.URL_QUERY) == "page=2&tracking=%5BFiltered%5D&token=%5BFiltered%5D" ) - assert _get_span_attr(attrs, SPANDATA.CLOUD_AVAILABILITY_ZONE) == "use1-az1" - # The container may already be warm (and the metadata cached) from an earlier - # test invoking the same function, so only assert that the second invocation - # is served from the cache. - metadata_requests = server.lambda_metadata_requests - assert metadata_requests >= 1 - - server.clear_envelopes() - lambda_client.invoke( - FunctionName="BasicOkSpanStreamingDataCollection", - Payload=json.dumps(payload), - ) - segment_spans = [s for s in server.span_items if s["is_segment"]] - - assert len(segment_spans) == 1 - assert ( - _get_span_attr(segment_spans[0]["attributes"], SPANDATA.CLOUD_AVAILABILITY_ZONE) - == "use1-az1" - ) - assert server.lambda_metadata_requests == metadata_requests def test_user_info_with_data_collection_user_info_off(lambda_client, test_environment): diff --git a/tests/integrations/aws_lambda/utils.py b/tests/integrations/aws_lambda/utils.py index 025b4a3ca6..386adce4aa 100644 --- a/tests/integrations/aws_lambda/utils.py +++ b/tests/integrations/aws_lambda/utils.py @@ -17,17 +17,14 @@ ) from constructs import Construct from fastapi import FastAPI, Request -from fastapi.responses import JSONResponse from scripts.build_aws_lambda_layer import DIST_PATH, build_packaged_zip -from sentry_sdk.integrations.aws_lambda.consts import LAMBDA_METADATA_PATH LAMBDA_FUNCTION_DIR = "./tests/integrations/aws_lambda/lambda_functions/" LAMBDA_FUNCTION_WITH_EMBEDDED_SDK_DIR = ( "./tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/" ) LAMBDA_FUNCTION_TIMEOUT = 10 -LAMBDA_METADATA_TOKEN = "test-token" SAM_PORT = 3001 # functions invoked through a published version or an alias instead of `$LATEST`: @@ -176,20 +173,6 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: ) environment = {"SENTRY_DSN": dsn} - if lambda_dir == "BasicOkSpanStreamingDataCollection": - # AWS exposes these env. vars when the metadata endpoint - # (`2026-01-15/metadata/execution-environment`) is available. - # We set et them only on the function that exercises `cloud.availability_zone`. - environment.update( - { - "SENTRY_TEST_AWS_LAMBDA_METADATA_API": ( - f"{get_host_ip()}:9999" - ), - "SENTRY_TEST_AWS_LAMBDA_METADATA_TOKEN": ( - LAMBDA_METADATA_TOKEN - ), - } - ) if lambda_dir in QUALIFIED_FUNCTIONS: qualifier, function_version = QUALIFIED_FUNCTIONS[lambda_dir] # SAM builds the invoked ARN from `AWS_LAMBDA_FUNCTION_NAME`, @@ -258,7 +241,6 @@ class SentryServerForTesting: def __init__(self, host="0.0.0.0", port=9999, log_level="warning"): self.envelopes = [] self.span_items = [] - self.lambda_metadata_requests = 0 self.host = host self.port = port self.log_level = log_level @@ -306,26 +288,6 @@ async def envelope(request: Request): return {"status": "ok"} - @self.app.get(LAMBDA_METADATA_PATH) - async def lambda_metadata(request: Request): - """ - Mirrors the Lambda execution-environment metadata endpoint - (`2026-01-15/metadata/execution-environment`) used to collect - `AvailabilityZoneID`, including its cache lifetime. - https://docs.aws.amazon.com/lambda/latest/dg/configuration-metadata-endpoint.html - """ - self.lambda_metadata_requests += 1 - - if request.headers.get("Authorization") != ( - f"Bearer {LAMBDA_METADATA_TOKEN}" - ): - return JSONResponse({"message": "Unauthorized"}, status_code=401) - - return JSONResponse( - {"AvailabilityZoneID": "use1-az1"}, - headers={"Cache-Control": "private, max-age=43200, immutable"}, - ) - def run_server(self): uvicorn.run(self.app, host=self.host, port=self.port, log_level=self.log_level)