From 208cf35247cd12cb8da79d7b4ae61d2b0f290622 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 4 Jun 2026 13:23:53 -0400 Subject: [PATCH 01/10] feat(chalice): Add span streaming support to Chalice integration Add span streaming support behind the trace_lifecycle=stream experiment flag. When enabled, start a segment span with Lambda/FaaS attributes for both HTTP view functions and event source handlers instead of setting a transaction name on the scope. Also adds aws_lambda to CLOUD_PLATFORM constants. Fixes PY-2312 Fixes #6010 --- sentry_sdk/integrations/chalice.py | 151 ++++++++++++--- .../integrations/cloud_resource_context.py | 1 + tests/integrations/chalice/test_chalice.py | 174 +++++++++++++++++- 3 files changed, 293 insertions(+), 33 deletions(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 48c9e81dc4..20be2392da 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -2,9 +2,18 @@ from functools import wraps import sentry_sdk +import sentry_sdk.traces +from sentry_sdk.consts import OP from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.aws_lambda import _make_request_event_processor +from sentry_sdk.integrations.cloud_resource_context import ( + CLOUD_PLATFORM, + CLOUD_PROVIDER, +) +from sentry_sdk.traces import SegmentSource from sentry_sdk.tracing import TransactionSource +from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( capture_internal_exceptions, event_from_exception, @@ -40,18 +49,38 @@ def __call__(self, event: "Any", context: "Any") -> "Any": scope.add_event_processor( _make_request_event_processor(event, context, configured_time) ) - try: - return ChaliceEventSourceHandler.__call__(self, event, context) - except Exception: - exc_info = sys.exc_info() - event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(event, hint=hint) - client.flush() - reraise(*exc_info) + + if has_span_streaming_enabled(client.options): + with sentry_sdk.traces.start_span( + name=context.function_name, + parent_span=None, + attributes=_get_lambda_span_attributes(context), + ): + try: + return ChaliceEventSourceHandler.__call__(self, event, context) + except Exception: + exc_info = sys.exc_info() + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + client.flush() + reraise(*exc_info) + else: + try: + return ChaliceEventSourceHandler.__call__(self, event, context) + except Exception: + exc_info = sys.exc_info() + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + client.flush() + reraise(*exc_info) def _get_view_function_response( @@ -63,11 +92,6 @@ def wrapped_view_function(**function_args: "Any") -> "Any": with sentry_sdk.isolation_scope() as scope: with capture_internal_exceptions(): configured_time = app.lambda_context.get_remaining_time_in_millis() - scope.set_transaction_name( - app.lambda_context.function_name, - source=TransactionSource.COMPONENT, - ) - scope.add_event_processor( _make_request_event_processor( app.current_request.to_dict(), @@ -75,26 +99,71 @@ def wrapped_view_function(**function_args: "Any") -> "Any": configured_time, ) ) - try: - return view_function(**function_args) - except Exception as exc: - if isinstance(exc, ChaliceViewError): - raise - exc_info = sys.exc_info() - event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, + + if has_span_streaming_enabled(client.options): + aws_context = app.lambda_context + request_dict = app.current_request.to_dict() + headers = request_dict.get("headers", {}) or {} + + header_attrs: "Dict[str, Any]" = {} + for header, value in _filter_headers( + headers, use_annotated_value=False + ).items(): + header_attrs[f"http.request.header.{header.lower()}"] = value + + additional_attrs: "Dict[str, Any]" = {} + if "method" in request_dict: + additional_attrs["http.request.method"] = request_dict["method"] + + with sentry_sdk.traces.start_span( + name=aws_context.function_name, + parent_span=None, + attributes={ + **_get_lambda_span_attributes(aws_context), + **header_attrs, + **additional_attrs, + }, + ): + try: + return view_function(**function_args) + except Exception as exc: + if isinstance(exc, ChaliceViewError): + raise + exc_info = sys.exc_info() + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + client.flush() + raise + else: + scope.set_transaction_name( + app.lambda_context.function_name, + source=TransactionSource.COMPONENT, ) - sentry_sdk.capture_event(event, hint=hint) - client.flush() - raise + try: + return view_function(**function_args) + except Exception as exc: + if isinstance(exc, ChaliceViewError): + raise + exc_info = sys.exc_info() + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + client.flush() + raise return wrapped_view_function # type: ignore class ChaliceIntegration(Integration): identifier = "chalice" + origin = f"auto.function.{identifier}" @staticmethod def setup_once() -> None: @@ -129,3 +198,25 @@ def sentry_event_response( RestAPIEventHandler._get_view_function_response = sentry_event_response # for everything else (like events) chalice.app.EventSourceHandler = EventSourceHandler + + +def _get_lambda_span_attributes(aws_context: "Any") -> "Dict[str, Any]": + invoked_arn = aws_context.invoked_function_arn + split_invoked_arn = invoked_arn.split(":") + aws_region = split_invoked_arn[3] if len(split_invoked_arn) > 3 else "unknown" + + return { + "sentry.op": OP.FUNCTION_AWS, + "sentry.origin": ChaliceIntegration.origin, + "sentry.span.source": SegmentSource.COMPONENT, + "cloud.platform": CLOUD_PLATFORM.AWS_LAMBDA, + "cloud.provider": CLOUD_PROVIDER.AWS, + "faas.name": aws_context.function_name, + "cloud.region": aws_region, + "cloud.resource_id": invoked_arn, + "aws.lambda.invoked_arn": invoked_arn, + "faas.invocation_id": aws_context.aws_request_id, + "faas.version": aws_context.function_version, + "aws.log.group.names": [aws_context.log_group_name], + "aws.log.stream.names": [aws_context.log_stream_name], + } diff --git a/sentry_sdk/integrations/cloud_resource_context.py b/sentry_sdk/integrations/cloud_resource_context.py index 87aa07ef4c..f6285d0a9b 100644 --- a/sentry_sdk/integrations/cloud_resource_context.py +++ b/sentry_sdk/integrations/cloud_resource_context.py @@ -48,6 +48,7 @@ class CLOUD_PLATFORM: # noqa: N801 """ AWS_EC2 = "aws_ec2" + AWS_LAMBDA = "aws_lambda" GCP_COMPUTE_ENGINE = "gcp_compute_engine" diff --git a/tests/integrations/chalice/test_chalice.py b/tests/integrations/chalice/test_chalice.py index f56ad716be..650408b154 100644 --- a/tests/integrations/chalice/test_chalice.py +++ b/tests/integrations/chalice/test_chalice.py @@ -5,11 +5,23 @@ from chalice.local import LambdaContext, LocalGateway from pytest_chalice.handlers import RequestHandler +import sentry_sdk from sentry_sdk import capture_message from sentry_sdk.integrations.chalice import CHALICE_VERSION, ChaliceIntegration from sentry_sdk.utils import parse_version +def _populate_lambda_context(context): + fn = context.function_name + context.invoked_function_arn = ( + f"arn:aws:lambda:us-east-1:123456789012:function:{fn}" + ) + context.log_group_name = f"/aws/lambda/{fn}" + context.log_stream_name = "2024/01/01/[$LATEST]abcdef1234567890" + context.aws_request_id = "test-request-id-1234" + return context + + def _generate_lambda_context(self): # Monkeypatch of the function _generate_lambda_context # from the class LocalGateway @@ -19,11 +31,12 @@ def _generate_lambda_context(self): timeout = 10 * 1000 else: timeout = self._config.lambda_timeout * 1000 - return LambdaContext( + context = LambdaContext( function_name=self._config.function_name, memory_size=self._config.lambda_memory_size, max_runtime_ms=timeout, ) + return _populate_lambda_context(context) @pytest.fixture @@ -89,8 +102,8 @@ def test_scheduled_event(app, lambda_context_args): def every_hour(event): raise Exception("schedule event!") - context = LambdaContext( - *lambda_context_args, max_runtime_ms=10000, time_source=time + context = _populate_lambda_context( + LambdaContext(*lambda_context_args, max_runtime_ms=10000, time_source=time) ) lambda_event = { @@ -160,3 +173,158 @@ def test_transaction( (event,) = events assert event["transaction"] == expected_transaction assert event["transaction_info"] == {"source": expected_source} + + +def _make_span_streaming_app(sentry_init): + sentry_init( + integrations=[ChaliceIntegration()], + traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream"}, + ) + app = Chalice(app_name="sentry_chalice") + + @app.route("/message") + def hi(): + capture_message("hi") + return {"status": "ok"} + + @app.route("/boom") + def boom(): + raise Exception("boom goes the dynamite!") + + LocalGateway._generate_lambda_context = _generate_lambda_context + + return app + + +def test_span_streaming_basic( + sentry_init, + capture_items, +): + app = _make_span_streaming_app(sentry_init) + client = RequestHandler(app) + items = capture_items("span") + + response = client.get("/message", headers={"X-Custom-Header": "test-value"}) + assert response.status_code == 200 + + sentry_sdk.flush() + + segment_spans = [s.payload for s in items if s.payload.get("is_segment")] + assert len(segment_spans) == 1 + span = segment_spans[0] + + attrs = span["attributes"] + assert attrs["sentry.op"] == "function.aws" + assert attrs["sentry.origin"] == "auto.function.chalice" + assert attrs["sentry.span.source"] == "component" + assert attrs["cloud.platform"] == "aws_lambda" + assert attrs["cloud.provider"] == "aws" + assert attrs["cloud.region"] == "us-east-1" + assert ( + attrs["cloud.resource_id"] + == "arn:aws:lambda:us-east-1:123456789012:function:api_handler" + ) + assert ( + attrs["aws.lambda.invoked_arn"] + == "arn:aws:lambda:us-east-1:123456789012:function:api_handler" + ) + assert attrs["faas.name"] == "api_handler" + assert attrs["faas.invocation_id"] == "test-request-id-1234" + assert attrs["faas.version"] == "$LATEST" + assert attrs["aws.log.group.names"] == ["/aws/lambda/api_handler"] + assert attrs["aws.log.stream.names"] == ["2024/01/01/[$LATEST]abcdef1234567890"] + assert attrs["http.request.method"] == "GET" + assert attrs["http.request.header.x-custom-header"] == "test-value" + + +def test_span_streaming_error( + sentry_init, + capture_items, +): + app = _make_span_streaming_app(sentry_init) + client = RequestHandler(app) + items = capture_items("event", "span") + + response = client.get("/boom") + assert response.status_code == 500 + + sentry_sdk.flush() + + error_items = [i for i in items if i.type == "event"] + span_items = [i for i in items if i.type == "span"] + + assert len(error_items) == 1 + assert len(span_items) >= 1 + + segment_spans = [s.payload for s in span_items if s.payload.get("is_segment")] + assert len(segment_spans) == 1 + + attrs = segment_spans[0]["attributes"] + assert attrs["sentry.op"] == "function.aws" + assert attrs["sentry.origin"] == "auto.function.chalice" + + +def test_span_streaming_scheduled_event( + sentry_init, + lambda_context_args, + capture_items, +): + sentry_init( + integrations=[ChaliceIntegration()], + traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream"}, + ) + app = Chalice(app_name="sentry_chalice") + + @app.schedule("rate(1 minutes)") + def every_hour(event): + raise Exception("schedule event!") + + items = capture_items("event", "span") + + context = _populate_lambda_context( + LambdaContext(*lambda_context_args, max_runtime_ms=10000, time_source=time) + ) + + lambda_event = { + "version": "0", + "account": "120987654312", + "region": "us-west-1", + "detail": {}, + "detail-type": "Scheduled Event", + "source": "aws.events", + "time": "1970-01-01T00:00:00Z", + "id": "event-id", + "resources": ["arn:aws:events:us-west-1:120987654312:rule/my-schedule"], + } + with pytest.raises(Exception) as exc_info: + every_hour(lambda_event, context=context) + assert str(exc_info.value) == "schedule event!" + + sentry_sdk.flush() + + span_items = [i for i in items if i.type == "span"] + segment_spans = [s.payload for s in span_items if s.payload.get("is_segment")] + assert len(segment_spans) == 1 + + attrs = segment_spans[0]["attributes"] + assert attrs["sentry.op"] == "function.aws" + assert attrs["sentry.origin"] == "auto.function.chalice" + assert attrs["sentry.span.source"] == "component" + assert attrs["cloud.platform"] == "aws_lambda" + assert attrs["cloud.provider"] == "aws" + assert attrs["cloud.region"] == "us-east-1" + assert ( + attrs["cloud.resource_id"] + == "arn:aws:lambda:us-east-1:123456789012:function:lambda_name" + ) + assert ( + attrs["aws.lambda.invoked_arn"] + == "arn:aws:lambda:us-east-1:123456789012:function:lambda_name" + ) + assert attrs["faas.name"] == "lambda_name" + assert attrs["faas.invocation_id"] == "test-request-id-1234" + assert attrs["faas.version"] == "$LATEST" + assert attrs["aws.log.group.names"] == ["/aws/lambda/lambda_name"] + assert attrs["aws.log.stream.names"] == ["2024/01/01/[$LATEST]abcdef1234567890"] From 6df89ee426101e021bc3c05772c0dfe5588f83f8 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 4 Jun 2026 13:34:11 -0400 Subject: [PATCH 02/10] cleanup --- sentry_sdk/integrations/chalice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 20be2392da..65afcb7668 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -103,7 +103,7 @@ def wrapped_view_function(**function_args: "Any") -> "Any": if has_span_streaming_enabled(client.options): aws_context = app.lambda_context request_dict = app.current_request.to_dict() - headers = request_dict.get("headers", {}) or {} + headers = request_dict.get("headers", {}) header_attrs: "Dict[str, Any]" = {} for header, value in _filter_headers( From d637c2f0eef6761afa1a2f0ba987dec4c1e3765f Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 4 Jun 2026 14:17:47 -0400 Subject: [PATCH 03/10] address potential data loss issue - https://github.com/getsentry/sentry-python/pull/6503\#discussion_r3357864384 --- sentry_sdk/integrations/chalice.py | 67 ++++++++++++---------- tests/integrations/chalice/test_chalice.py | 25 ++++++++ 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 65afcb7668..08545b28ba 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -11,7 +11,7 @@ CLOUD_PLATFORM, CLOUD_PROVIDER, ) -from sentry_sdk.traces import SegmentSource +from sentry_sdk.traces import SegmentSource, SpanStatus from sentry_sdk.tracing import TransactionSource from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( @@ -51,23 +51,27 @@ def __call__(self, event: "Any", context: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): - with sentry_sdk.traces.start_span( + span = sentry_sdk.traces.start_span( name=context.function_name, parent_span=None, attributes=_get_lambda_span_attributes(context), - ): - try: - return ChaliceEventSourceHandler.__call__(self, event, context) - except Exception: - exc_info = sys.exc_info() - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(sentry_event, hint=hint) - client.flush() - reraise(*exc_info) + ) + try: + return ChaliceEventSourceHandler.__call__(self, event, context) + except Exception: + exc_info = sys.exc_info() + span.status = SpanStatus.ERROR.value + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + reraise(*exc_info) + finally: + span.end() + client.flush() + else: try: return ChaliceEventSourceHandler.__call__(self, event, context) @@ -115,7 +119,7 @@ def wrapped_view_function(**function_args: "Any") -> "Any": if "method" in request_dict: additional_attrs["http.request.method"] = request_dict["method"] - with sentry_sdk.traces.start_span( + span = sentry_sdk.traces.start_span( name=aws_context.function_name, parent_span=None, attributes={ @@ -123,21 +127,24 @@ def wrapped_view_function(**function_args: "Any") -> "Any": **header_attrs, **additional_attrs, }, - ): - try: - return view_function(**function_args) - except Exception as exc: - if isinstance(exc, ChaliceViewError): - raise - exc_info = sys.exc_info() - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(sentry_event, hint=hint) - client.flush() + ) + try: + return view_function(**function_args) + except Exception as exc: + if isinstance(exc, ChaliceViewError): raise + exc_info = sys.exc_info() + span.status = SpanStatus.ERROR.value + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + raise + finally: + span.end() + client.flush() else: scope.set_transaction_name( app.lambda_context.function_name, diff --git a/tests/integrations/chalice/test_chalice.py b/tests/integrations/chalice/test_chalice.py index 650408b154..522f69155e 100644 --- a/tests/integrations/chalice/test_chalice.py +++ b/tests/integrations/chalice/test_chalice.py @@ -236,6 +236,7 @@ def test_span_streaming_basic( assert attrs["aws.log.stream.names"] == ["2024/01/01/[$LATEST]abcdef1234567890"] assert attrs["http.request.method"] == "GET" assert attrs["http.request.header.x-custom-header"] == "test-value" + assert span["status"] == "ok" def test_span_streaming_error( @@ -263,6 +264,29 @@ def test_span_streaming_error( attrs = segment_spans[0]["attributes"] assert attrs["sentry.op"] == "function.aws" assert attrs["sentry.origin"] == "auto.function.chalice" + assert segment_spans[0]["status"] == "error" + + +def test_span_streaming_error_flush_ordering( + sentry_init, + capture_items, +): + """The handler's own client.flush() must send the segment span. + + If flush runs before the span ends, the segment won't be in the + transport without an extra sentry_sdk.flush() call after the request. + On Lambda, the worker can freeze right after the response, so there's + no second chance. + """ + app = _make_span_streaming_app(sentry_init) + client = RequestHandler(app) + items = capture_items("span") + + response = client.get("/boom") + assert response.status_code == 500 + + segment_spans = [s.payload for s in items if s.payload.get("is_segment")] + assert len(segment_spans) == 1 def test_span_streaming_scheduled_event( @@ -328,3 +352,4 @@ def every_hour(event): assert attrs["faas.version"] == "$LATEST" assert attrs["aws.log.group.names"] == ["/aws/lambda/lambda_name"] assert attrs["aws.log.stream.names"] == ["2024/01/01/[$LATEST]abcdef1234567890"] + assert segment_spans[0]["status"] == "error" From e658841a03cc9ac94f9fdf9e97e134af192f8434 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 11 Jun 2026 14:25:57 -0400 Subject: [PATCH 04/10] Version that supports tracing when the AWS lambda integration is enabled and where the Chalice integration is running on its own --- sentry_sdk/integrations/chalice.py | 142 ++++++++++++++------- tests/integrations/chalice/test_chalice.py | 139 ++++++++++++++++++++ 2 files changed, 237 insertions(+), 44 deletions(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 08545b28ba..e8a1684cd5 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -11,7 +11,12 @@ CLOUD_PLATFORM, CLOUD_PROVIDER, ) -from sentry_sdk.traces import SegmentSource, SpanStatus +from sentry_sdk.traces import ( + SegmentSource, + SpanStatus, + StreamedSpan, + get_current_span, +) from sentry_sdk.tracing import TransactionSource from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( @@ -51,26 +56,47 @@ def __call__(self, event: "Any", context: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): - span = sentry_sdk.traces.start_span( - name=context.function_name, - parent_span=None, - attributes=_get_lambda_span_attributes(context), - ) - try: - return ChaliceEventSourceHandler.__call__(self, event, context) - except Exception: - exc_info = sys.exc_info() - span.status = SpanStatus.ERROR.value - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, + current_span = get_current_span() + if type(current_span) is StreamedSpan: + # A segment already exists (created by the AWS Lambda + # integration), so decorate it with Chalice attributes + # instead of creating a duplicate root segment. The AWS + # Lambda integration owns the span lifecycle (end + flush). + segment = current_span._segment + segment.set_attributes(_get_lambda_span_attributes(context)) + try: + return ChaliceEventSourceHandler.__call__(self, event, context) + except Exception: + exc_info = sys.exc_info() + segment.status = SpanStatus.ERROR.value + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + reraise(*exc_info) + else: + span = sentry_sdk.traces.start_span( + name=context.function_name, + parent_span=None, + attributes=_get_lambda_span_attributes(context), ) - sentry_sdk.capture_event(sentry_event, hint=hint) - reraise(*exc_info) - finally: - span.end() - client.flush() + try: + return ChaliceEventSourceHandler.__call__(self, event, context) + except Exception: + exc_info = sys.exc_info() + span.status = SpanStatus.ERROR.value + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + reraise(*exc_info) + finally: + span.end() + client.flush() else: try: @@ -119,32 +145,60 @@ def wrapped_view_function(**function_args: "Any") -> "Any": if "method" in request_dict: additional_attrs["http.request.method"] = request_dict["method"] - span = sentry_sdk.traces.start_span( - name=aws_context.function_name, - parent_span=None, - attributes={ - **_get_lambda_span_attributes(aws_context), - **header_attrs, - **additional_attrs, - }, - ) - try: - return view_function(**function_args) - except Exception as exc: - if isinstance(exc, ChaliceViewError): + attributes = { + **_get_lambda_span_attributes(aws_context), + **header_attrs, + **additional_attrs, + } + + current_span = get_current_span() + if type(current_span) is StreamedSpan: + # A segment already exists (created by the AWS Lambda + # integration), so decorate it with Chalice attributes + # instead of creating a duplicate root segment. The AWS + # Lambda integration owns the span lifecycle (end + flush), + # but Chalice converts unhandled view exceptions into 500 + # responses, so the error must be captured here. + segment = current_span._segment + segment.set_attributes(attributes) + try: + return view_function(**function_args) + except Exception as exc: + if isinstance(exc, ChaliceViewError): + raise + exc_info = sys.exc_info() + segment.status = SpanStatus.ERROR.value + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) raise - exc_info = sys.exc_info() - span.status = SpanStatus.ERROR.value - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, + else: + sentry_sdk.traces.continue_trace(headers) + span = sentry_sdk.traces.start_span( + name=aws_context.function_name, + parent_span=None, + attributes=attributes, ) - sentry_sdk.capture_event(sentry_event, hint=hint) - raise - finally: - span.end() - client.flush() + try: + return view_function(**function_args) + except Exception as exc: + if isinstance(exc, ChaliceViewError): + raise + exc_info = sys.exc_info() + span.status = SpanStatus.ERROR.value + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + raise + finally: + span.end() + client.flush() else: scope.set_transaction_name( app.lambda_context.function_name, diff --git a/tests/integrations/chalice/test_chalice.py b/tests/integrations/chalice/test_chalice.py index 522f69155e..8e156b14df 100644 --- a/tests/integrations/chalice/test_chalice.py +++ b/tests/integrations/chalice/test_chalice.py @@ -239,6 +239,33 @@ def test_span_streaming_basic( assert span["status"] == "ok" +def test_span_streaming_continue_trace( + sentry_init, + capture_items, +): + """When incoming headers contain sentry-trace, the standalone Chalice + path (no AWS Lambda span) continues the trace.""" + app = _make_span_streaming_app(sentry_init) + client = RequestHandler(app) + items = capture_items("span") + + trace_id = "471a43a4192642f0b136d5159a501701" + parent_span_id = "a00bc7e6637abd57" + sentry_trace_header = f"{trace_id}-{parent_span_id}-1" + + response = client.get( + "/message", headers={"sentry-trace": sentry_trace_header} + ) + assert response.status_code == 200 + + sentry_sdk.flush() + + segment_spans = [s.payload for s in items if s.payload.get("is_segment")] + assert len(segment_spans) == 1 + assert segment_spans[0]["trace_id"] == trace_id + assert segment_spans[0]["parent_span_id"] == parent_span_id + + def test_span_streaming_error( sentry_init, capture_items, @@ -289,6 +316,118 @@ def test_span_streaming_error_flush_ordering( assert len(segment_spans) == 1 +def test_span_streaming_existing_span( + sentry_init, + capture_items, +): + """When a segment already exists (e.g. created by the AWS Lambda + integration), Chalice decorates it instead of creating a duplicate.""" + app = _make_span_streaming_app(sentry_init) + client = RequestHandler(app) + items = capture_items("span") + + with sentry_sdk.traces.start_span( + name="lambda_segment", + parent_span=None, + attributes={"sentry.origin": "auto.function.aws_lambda"}, + ): + response = client.get("/message") + assert response.status_code == 200 + + sentry_sdk.flush() + + segment_spans = [s.payload for s in items if s.payload.get("is_segment")] + assert len(segment_spans) == 1 + span = segment_spans[0] + + attrs = span["attributes"] + assert attrs["sentry.origin"] == "auto.function.chalice" + assert attrs["sentry.op"] == "function.aws" + assert attrs["faas.name"] == "api_handler" + assert span["status"] == "ok" + + +def test_span_streaming_existing_span_error( + sentry_init, + capture_items, +): + app = _make_span_streaming_app(sentry_init) + client = RequestHandler(app) + items = capture_items("event", "span") + + with sentry_sdk.traces.start_span( + name="lambda_segment", + parent_span=None, + attributes={"sentry.origin": "auto.function.aws_lambda"}, + ): + response = client.get("/boom") + assert response.status_code == 500 + + sentry_sdk.flush() + + error_items = [i for i in items if i.type == "event"] + assert len(error_items) == 1 + + segment_spans = [ + s.payload for s in items if s.type == "span" and s.payload.get("is_segment") + ] + assert len(segment_spans) == 1 + assert segment_spans[0]["attributes"]["sentry.origin"] == "auto.function.chalice" + assert segment_spans[0]["status"] == "error" + + +def test_span_streaming_existing_span_scheduled_event( + sentry_init, + lambda_context_args, + capture_items, +): + sentry_init( + integrations=[ChaliceIntegration()], + traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream"}, + ) + app = Chalice(app_name="sentry_chalice") + + @app.schedule("rate(1 minutes)") + def every_hour(event): + raise Exception("schedule event!") + + items = capture_items("event", "span") + + context = _populate_lambda_context( + LambdaContext(*lambda_context_args, max_runtime_ms=10000, time_source=time) + ) + + lambda_event = { + "version": "0", + "account": "120987654312", + "region": "us-west-1", + "detail": {}, + "detail-type": "Scheduled Event", + "source": "aws.events", + "time": "1970-01-01T00:00:00Z", + "id": "event-id", + "resources": ["arn:aws:events:us-west-1:120987654312:rule/my-schedule"], + } + with sentry_sdk.traces.start_span( + name="lambda_segment", + parent_span=None, + attributes={"sentry.origin": "auto.function.aws_lambda"}, + ): + with pytest.raises(Exception) as exc_info: + every_hour(lambda_event, context=context) + assert str(exc_info.value) == "schedule event!" + + sentry_sdk.flush() + + segment_spans = [ + s.payload for s in items if s.type == "span" and s.payload.get("is_segment") + ] + assert len(segment_spans) == 1 + assert segment_spans[0]["attributes"]["sentry.origin"] == "auto.function.chalice" + assert segment_spans[0]["status"] == "error" + + def test_span_streaming_scheduled_event( sentry_init, lambda_context_args, From 65232d25a99f4b70cf18b4b70b8239b399cc7c39 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 11 Jun 2026 14:52:02 -0400 Subject: [PATCH 05/10] Address code review comments. Removes the creation of new spans but instead updates the segment of the current span should one be present (this happens if the AWS Lambda integration is also enabled and would be what creates the initial span). --- sentry_sdk/integrations/chalice.py | 166 +++++---------- tests/integrations/chalice/test_chalice.py | 237 --------------------- 2 files changed, 50 insertions(+), 353 deletions(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index e8a1684cd5..75cd625b0b 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -55,62 +55,18 @@ def __call__(self, event: "Any", context: "Any") -> "Any": _make_request_event_processor(event, context, configured_time) ) - if has_span_streaming_enabled(client.options): - current_span = get_current_span() - if type(current_span) is StreamedSpan: - # A segment already exists (created by the AWS Lambda - # integration), so decorate it with Chalice attributes - # instead of creating a duplicate root segment. The AWS - # Lambda integration owns the span lifecycle (end + flush). - segment = current_span._segment - segment.set_attributes(_get_lambda_span_attributes(context)) - try: - return ChaliceEventSourceHandler.__call__(self, event, context) - except Exception: - exc_info = sys.exc_info() - segment.status = SpanStatus.ERROR.value - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(sentry_event, hint=hint) - reraise(*exc_info) - else: - span = sentry_sdk.traces.start_span( - name=context.function_name, - parent_span=None, - attributes=_get_lambda_span_attributes(context), - ) - try: - return ChaliceEventSourceHandler.__call__(self, event, context) - except Exception: - exc_info = sys.exc_info() - span.status = SpanStatus.ERROR.value - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(sentry_event, hint=hint) - reraise(*exc_info) - finally: - span.end() - client.flush() - - else: - try: - return ChaliceEventSourceHandler.__call__(self, event, context) - except Exception: - exc_info = sys.exc_info() - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(sentry_event, hint=hint) - client.flush() - reraise(*exc_info) + try: + return ChaliceEventSourceHandler.__call__(self, event, context) + except Exception: + exc_info = sys.exc_info() + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + client.flush() + reraise(*exc_info) def _get_view_function_response( @@ -131,74 +87,52 @@ def wrapped_view_function(**function_args: "Any") -> "Any": ) if has_span_streaming_enabled(client.options): - aws_context = app.lambda_context - request_dict = app.current_request.to_dict() - headers = request_dict.get("headers", {}) - - header_attrs: "Dict[str, Any]" = {} - for header, value in _filter_headers( - headers, use_annotated_value=False - ).items(): - header_attrs[f"http.request.header.{header.lower()}"] = value - - additional_attrs: "Dict[str, Any]" = {} - if "method" in request_dict: - additional_attrs["http.request.method"] = request_dict["method"] - - attributes = { - **_get_lambda_span_attributes(aws_context), - **header_attrs, - **additional_attrs, - } - current_span = get_current_span() + segment = None if type(current_span) is StreamedSpan: # A segment already exists (created by the AWS Lambda # integration), so decorate it with Chalice attributes - # instead of creating a duplicate root segment. The AWS - # Lambda integration owns the span lifecycle (end + flush), - # but Chalice converts unhandled view exceptions into 500 - # responses, so the error must be captured here. + # The AWS Lambda integration owns the span lifecycle + # (end + flush), but Chalice converts unhandled view exceptions + # into 500 responses, so the error must be captured here. + aws_context = app.lambda_context + request_dict = app.current_request.to_dict() + headers = request_dict.get("headers", {}) + + header_attrs: "Dict[str, Any]" = {} + for header, value in _filter_headers( + headers, use_annotated_value=False + ).items(): + header_attrs[f"http.request.header.{header.lower()}"] = value + + additional_attrs: "Dict[str, Any]" = {} + if "method" in request_dict: + additional_attrs["http.request.method"] = request_dict["method"] + + attributes = { + **_get_lambda_span_attributes(aws_context), + **header_attrs, + **additional_attrs, + } + segment = current_span._segment segment.set_attributes(attributes) - try: - return view_function(**function_args) - except Exception as exc: - if isinstance(exc, ChaliceViewError): - raise - exc_info = sys.exc_info() - segment.status = SpanStatus.ERROR.value - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(sentry_event, hint=hint) + + try: + return view_function(**function_args) + except Exception as exc: + if isinstance(exc, ChaliceViewError): raise - else: - sentry_sdk.traces.continue_trace(headers) - span = sentry_sdk.traces.start_span( - name=aws_context.function_name, - parent_span=None, - attributes=attributes, + exc_info = sys.exc_info() + if segment: + segment.status = SpanStatus.ERROR.value + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, ) - try: - return view_function(**function_args) - except Exception as exc: - if isinstance(exc, ChaliceViewError): - raise - exc_info = sys.exc_info() - span.status = SpanStatus.ERROR.value - sentry_event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "chalice", "handled": False}, - ) - sentry_sdk.capture_event(sentry_event, hint=hint) - raise - finally: - span.end() - client.flush() + sentry_sdk.capture_event(sentry_event, hint=hint) + raise else: scope.set_transaction_name( app.lambda_context.function_name, diff --git a/tests/integrations/chalice/test_chalice.py b/tests/integrations/chalice/test_chalice.py index 8e156b14df..9ed33d8dc4 100644 --- a/tests/integrations/chalice/test_chalice.py +++ b/tests/integrations/chalice/test_chalice.py @@ -197,125 +197,6 @@ def boom(): return app -def test_span_streaming_basic( - sentry_init, - capture_items, -): - app = _make_span_streaming_app(sentry_init) - client = RequestHandler(app) - items = capture_items("span") - - response = client.get("/message", headers={"X-Custom-Header": "test-value"}) - assert response.status_code == 200 - - sentry_sdk.flush() - - segment_spans = [s.payload for s in items if s.payload.get("is_segment")] - assert len(segment_spans) == 1 - span = segment_spans[0] - - attrs = span["attributes"] - assert attrs["sentry.op"] == "function.aws" - assert attrs["sentry.origin"] == "auto.function.chalice" - assert attrs["sentry.span.source"] == "component" - assert attrs["cloud.platform"] == "aws_lambda" - assert attrs["cloud.provider"] == "aws" - assert attrs["cloud.region"] == "us-east-1" - assert ( - attrs["cloud.resource_id"] - == "arn:aws:lambda:us-east-1:123456789012:function:api_handler" - ) - assert ( - attrs["aws.lambda.invoked_arn"] - == "arn:aws:lambda:us-east-1:123456789012:function:api_handler" - ) - assert attrs["faas.name"] == "api_handler" - assert attrs["faas.invocation_id"] == "test-request-id-1234" - assert attrs["faas.version"] == "$LATEST" - assert attrs["aws.log.group.names"] == ["/aws/lambda/api_handler"] - assert attrs["aws.log.stream.names"] == ["2024/01/01/[$LATEST]abcdef1234567890"] - assert attrs["http.request.method"] == "GET" - assert attrs["http.request.header.x-custom-header"] == "test-value" - assert span["status"] == "ok" - - -def test_span_streaming_continue_trace( - sentry_init, - capture_items, -): - """When incoming headers contain sentry-trace, the standalone Chalice - path (no AWS Lambda span) continues the trace.""" - app = _make_span_streaming_app(sentry_init) - client = RequestHandler(app) - items = capture_items("span") - - trace_id = "471a43a4192642f0b136d5159a501701" - parent_span_id = "a00bc7e6637abd57" - sentry_trace_header = f"{trace_id}-{parent_span_id}-1" - - response = client.get( - "/message", headers={"sentry-trace": sentry_trace_header} - ) - assert response.status_code == 200 - - sentry_sdk.flush() - - segment_spans = [s.payload for s in items if s.payload.get("is_segment")] - assert len(segment_spans) == 1 - assert segment_spans[0]["trace_id"] == trace_id - assert segment_spans[0]["parent_span_id"] == parent_span_id - - -def test_span_streaming_error( - sentry_init, - capture_items, -): - app = _make_span_streaming_app(sentry_init) - client = RequestHandler(app) - items = capture_items("event", "span") - - response = client.get("/boom") - assert response.status_code == 500 - - sentry_sdk.flush() - - error_items = [i for i in items if i.type == "event"] - span_items = [i for i in items if i.type == "span"] - - assert len(error_items) == 1 - assert len(span_items) >= 1 - - segment_spans = [s.payload for s in span_items if s.payload.get("is_segment")] - assert len(segment_spans) == 1 - - attrs = segment_spans[0]["attributes"] - assert attrs["sentry.op"] == "function.aws" - assert attrs["sentry.origin"] == "auto.function.chalice" - assert segment_spans[0]["status"] == "error" - - -def test_span_streaming_error_flush_ordering( - sentry_init, - capture_items, -): - """The handler's own client.flush() must send the segment span. - - If flush runs before the span ends, the segment won't be in the - transport without an extra sentry_sdk.flush() call after the request. - On Lambda, the worker can freeze right after the response, so there's - no second chance. - """ - app = _make_span_streaming_app(sentry_init) - client = RequestHandler(app) - items = capture_items("span") - - response = client.get("/boom") - assert response.status_code == 500 - - segment_spans = [s.payload for s in items if s.payload.get("is_segment")] - assert len(segment_spans) == 1 - - def test_span_streaming_existing_span( sentry_init, capture_items, @@ -374,121 +255,3 @@ def test_span_streaming_existing_span_error( assert len(segment_spans) == 1 assert segment_spans[0]["attributes"]["sentry.origin"] == "auto.function.chalice" assert segment_spans[0]["status"] == "error" - - -def test_span_streaming_existing_span_scheduled_event( - sentry_init, - lambda_context_args, - capture_items, -): - sentry_init( - integrations=[ChaliceIntegration()], - traces_sample_rate=1.0, - _experiments={"trace_lifecycle": "stream"}, - ) - app = Chalice(app_name="sentry_chalice") - - @app.schedule("rate(1 minutes)") - def every_hour(event): - raise Exception("schedule event!") - - items = capture_items("event", "span") - - context = _populate_lambda_context( - LambdaContext(*lambda_context_args, max_runtime_ms=10000, time_source=time) - ) - - lambda_event = { - "version": "0", - "account": "120987654312", - "region": "us-west-1", - "detail": {}, - "detail-type": "Scheduled Event", - "source": "aws.events", - "time": "1970-01-01T00:00:00Z", - "id": "event-id", - "resources": ["arn:aws:events:us-west-1:120987654312:rule/my-schedule"], - } - with sentry_sdk.traces.start_span( - name="lambda_segment", - parent_span=None, - attributes={"sentry.origin": "auto.function.aws_lambda"}, - ): - with pytest.raises(Exception) as exc_info: - every_hour(lambda_event, context=context) - assert str(exc_info.value) == "schedule event!" - - sentry_sdk.flush() - - segment_spans = [ - s.payload for s in items if s.type == "span" and s.payload.get("is_segment") - ] - assert len(segment_spans) == 1 - assert segment_spans[0]["attributes"]["sentry.origin"] == "auto.function.chalice" - assert segment_spans[0]["status"] == "error" - - -def test_span_streaming_scheduled_event( - sentry_init, - lambda_context_args, - capture_items, -): - sentry_init( - integrations=[ChaliceIntegration()], - traces_sample_rate=1.0, - _experiments={"trace_lifecycle": "stream"}, - ) - app = Chalice(app_name="sentry_chalice") - - @app.schedule("rate(1 minutes)") - def every_hour(event): - raise Exception("schedule event!") - - items = capture_items("event", "span") - - context = _populate_lambda_context( - LambdaContext(*lambda_context_args, max_runtime_ms=10000, time_source=time) - ) - - lambda_event = { - "version": "0", - "account": "120987654312", - "region": "us-west-1", - "detail": {}, - "detail-type": "Scheduled Event", - "source": "aws.events", - "time": "1970-01-01T00:00:00Z", - "id": "event-id", - "resources": ["arn:aws:events:us-west-1:120987654312:rule/my-schedule"], - } - with pytest.raises(Exception) as exc_info: - every_hour(lambda_event, context=context) - assert str(exc_info.value) == "schedule event!" - - sentry_sdk.flush() - - span_items = [i for i in items if i.type == "span"] - segment_spans = [s.payload for s in span_items if s.payload.get("is_segment")] - assert len(segment_spans) == 1 - - attrs = segment_spans[0]["attributes"] - assert attrs["sentry.op"] == "function.aws" - assert attrs["sentry.origin"] == "auto.function.chalice" - assert attrs["sentry.span.source"] == "component" - assert attrs["cloud.platform"] == "aws_lambda" - assert attrs["cloud.provider"] == "aws" - assert attrs["cloud.region"] == "us-east-1" - assert ( - attrs["cloud.resource_id"] - == "arn:aws:lambda:us-east-1:123456789012:function:lambda_name" - ) - assert ( - attrs["aws.lambda.invoked_arn"] - == "arn:aws:lambda:us-east-1:123456789012:function:lambda_name" - ) - assert attrs["faas.name"] == "lambda_name" - assert attrs["faas.invocation_id"] == "test-request-id-1234" - assert attrs["faas.version"] == "$LATEST" - assert attrs["aws.log.group.names"] == ["/aws/lambda/lambda_name"] - assert attrs["aws.log.stream.names"] == ["2024/01/01/[$LATEST]abcdef1234567890"] - assert segment_spans[0]["status"] == "error" From 906158ff96e8c749e4f894dfe508187cbec02947 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 11 Jun 2026 14:56:13 -0400 Subject: [PATCH 06/10] Need to manually flush the client if span streaming is enabled by the AWS lambda integration is not --- sentry_sdk/integrations/chalice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 75cd625b0b..4b1a97d3a7 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -132,6 +132,8 @@ def wrapped_view_function(**function_args: "Any") -> "Any": mechanism={"type": "chalice", "handled": False}, ) sentry_sdk.capture_event(sentry_event, hint=hint) + if not segment: + client.flush() raise else: scope.set_transaction_name( From dcf2e10c8e9edc79686d585d3d5ee97360995272 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 11 Jun 2026 15:02:38 -0400 Subject: [PATCH 07/10] revert changes to the method --- sentry_sdk/integrations/chalice.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 4b1a97d3a7..ceb71770f8 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -54,17 +54,16 @@ def __call__(self, event: "Any", context: "Any") -> "Any": scope.add_event_processor( _make_request_event_processor(event, context, configured_time) ) - try: return ChaliceEventSourceHandler.__call__(self, event, context) except Exception: exc_info = sys.exc_info() - sentry_event, hint = event_from_exception( + event, hint = event_from_exception( exc_info, client_options=client.options, mechanism={"type": "chalice", "handled": False}, ) - sentry_sdk.capture_event(sentry_event, hint=hint) + sentry_sdk.capture_event(event, hint=hint) client.flush() reraise(*exc_info) @@ -132,7 +131,7 @@ def wrapped_view_function(**function_args: "Any") -> "Any": mechanism={"type": "chalice", "handled": False}, ) sentry_sdk.capture_event(sentry_event, hint=hint) - if not segment: + if segment is None: client.flush() raise else: From 596ad365dc82b7eeac41fd7c15046a020bf87f9c Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 12 Jun 2026 09:39:43 -0400 Subject: [PATCH 08/10] Cleanup --- sentry_sdk/integrations/chalice.py | 25 +--------------------- tests/integrations/chalice/test_chalice.py | 6 +++++- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index ceb71770f8..7143790e77 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -94,7 +94,6 @@ def wrapped_view_function(**function_args: "Any") -> "Any": # The AWS Lambda integration owns the span lifecycle # (end + flush), but Chalice converts unhandled view exceptions # into 500 responses, so the error must be captured here. - aws_context = app.lambda_context request_dict = app.current_request.to_dict() headers = request_dict.get("headers", {}) @@ -109,7 +108,7 @@ def wrapped_view_function(**function_args: "Any") -> "Any": additional_attrs["http.request.method"] = request_dict["method"] attributes = { - **_get_lambda_span_attributes(aws_context), + "sentry.origin": ChaliceIntegration.origin, **header_attrs, **additional_attrs, } @@ -194,25 +193,3 @@ def sentry_event_response( RestAPIEventHandler._get_view_function_response = sentry_event_response # for everything else (like events) chalice.app.EventSourceHandler = EventSourceHandler - - -def _get_lambda_span_attributes(aws_context: "Any") -> "Dict[str, Any]": - invoked_arn = aws_context.invoked_function_arn - split_invoked_arn = invoked_arn.split(":") - aws_region = split_invoked_arn[3] if len(split_invoked_arn) > 3 else "unknown" - - return { - "sentry.op": OP.FUNCTION_AWS, - "sentry.origin": ChaliceIntegration.origin, - "sentry.span.source": SegmentSource.COMPONENT, - "cloud.platform": CLOUD_PLATFORM.AWS_LAMBDA, - "cloud.provider": CLOUD_PROVIDER.AWS, - "faas.name": aws_context.function_name, - "cloud.region": aws_region, - "cloud.resource_id": invoked_arn, - "aws.lambda.invoked_arn": invoked_arn, - "faas.invocation_id": aws_context.aws_request_id, - "faas.version": aws_context.function_version, - "aws.log.group.names": [aws_context.log_group_name], - "aws.log.stream.names": [aws_context.log_stream_name], - } diff --git a/tests/integrations/chalice/test_chalice.py b/tests/integrations/chalice/test_chalice.py index 9ed33d8dc4..49fc3779da 100644 --- a/tests/integrations/chalice/test_chalice.py +++ b/tests/integrations/chalice/test_chalice.py @@ -210,7 +210,11 @@ def test_span_streaming_existing_span( with sentry_sdk.traces.start_span( name="lambda_segment", parent_span=None, - attributes={"sentry.origin": "auto.function.aws_lambda"}, + attributes={ + "sentry.origin": "auto.function.aws_lambda", + "sentry.op": "function.aws", + "faas.name": "api_handler", + }, ): response = client.get("/message") assert response.status_code == 200 From 13010e595abcf7a4bd3dcdaa635d1913d2c0fb38 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 12 Jun 2026 09:46:24 -0400 Subject: [PATCH 09/10] remove unused imports --- sentry_sdk/integrations/chalice.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 7143790e77..1b31bd8ad2 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -2,17 +2,11 @@ from functools import wraps import sentry_sdk -import sentry_sdk.traces from sentry_sdk.consts import OP from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.aws_lambda import _make_request_event_processor -from sentry_sdk.integrations.cloud_resource_context import ( - CLOUD_PLATFORM, - CLOUD_PROVIDER, -) from sentry_sdk.traces import ( - SegmentSource, SpanStatus, StreamedSpan, get_current_span, From c6d236039aa94ceae174b9d35cc1ef52b90fb056 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 12 Jun 2026 09:50:36 -0400 Subject: [PATCH 10/10] . --- sentry_sdk/integrations/chalice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry_sdk/integrations/chalice.py b/sentry_sdk/integrations/chalice.py index 1b31bd8ad2..9baa0e5cdd 100644 --- a/sentry_sdk/integrations/chalice.py +++ b/sentry_sdk/integrations/chalice.py @@ -2,7 +2,6 @@ from functools import wraps import sentry_sdk -from sentry_sdk.consts import OP from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.integrations._wsgi_common import _filter_headers from sentry_sdk.integrations.aws_lambda import _make_request_event_processor