Skip to content

Commit 208cf35

Browse files
committed
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
1 parent 359ba7e commit 208cf35

3 files changed

Lines changed: 293 additions & 33 deletions

File tree

sentry_sdk/integrations/chalice.py

Lines changed: 121 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@
22
from functools import wraps
33

44
import sentry_sdk
5+
import sentry_sdk.traces
6+
from sentry_sdk.consts import OP
57
from sentry_sdk.integrations import DidNotEnable, Integration
8+
from sentry_sdk.integrations._wsgi_common import _filter_headers
69
from sentry_sdk.integrations.aws_lambda import _make_request_event_processor
10+
from sentry_sdk.integrations.cloud_resource_context import (
11+
CLOUD_PLATFORM,
12+
CLOUD_PROVIDER,
13+
)
14+
from sentry_sdk.traces import SegmentSource
715
from sentry_sdk.tracing import TransactionSource
16+
from sentry_sdk.tracing_utils import has_span_streaming_enabled
817
from sentry_sdk.utils import (
918
capture_internal_exceptions,
1019
event_from_exception,
@@ -40,18 +49,38 @@ def __call__(self, event: "Any", context: "Any") -> "Any":
4049
scope.add_event_processor(
4150
_make_request_event_processor(event, context, configured_time)
4251
)
43-
try:
44-
return ChaliceEventSourceHandler.__call__(self, event, context)
45-
except Exception:
46-
exc_info = sys.exc_info()
47-
event, hint = event_from_exception(
48-
exc_info,
49-
client_options=client.options,
50-
mechanism={"type": "chalice", "handled": False},
51-
)
52-
sentry_sdk.capture_event(event, hint=hint)
53-
client.flush()
54-
reraise(*exc_info)
52+
53+
if has_span_streaming_enabled(client.options):
54+
with sentry_sdk.traces.start_span(
55+
name=context.function_name,
56+
parent_span=None,
57+
attributes=_get_lambda_span_attributes(context),
58+
):
59+
try:
60+
return ChaliceEventSourceHandler.__call__(self, event, context)
61+
except Exception:
62+
exc_info = sys.exc_info()
63+
sentry_event, hint = event_from_exception(
64+
exc_info,
65+
client_options=client.options,
66+
mechanism={"type": "chalice", "handled": False},
67+
)
68+
sentry_sdk.capture_event(sentry_event, hint=hint)
69+
client.flush()
70+
reraise(*exc_info)
71+
else:
72+
try:
73+
return ChaliceEventSourceHandler.__call__(self, event, context)
74+
except Exception:
75+
exc_info = sys.exc_info()
76+
sentry_event, hint = event_from_exception(
77+
exc_info,
78+
client_options=client.options,
79+
mechanism={"type": "chalice", "handled": False},
80+
)
81+
sentry_sdk.capture_event(sentry_event, hint=hint)
82+
client.flush()
83+
reraise(*exc_info)
5584

5685

5786
def _get_view_function_response(
@@ -63,38 +92,78 @@ def wrapped_view_function(**function_args: "Any") -> "Any":
6392
with sentry_sdk.isolation_scope() as scope:
6493
with capture_internal_exceptions():
6594
configured_time = app.lambda_context.get_remaining_time_in_millis()
66-
scope.set_transaction_name(
67-
app.lambda_context.function_name,
68-
source=TransactionSource.COMPONENT,
69-
)
70-
7195
scope.add_event_processor(
7296
_make_request_event_processor(
7397
app.current_request.to_dict(),
7498
app.lambda_context,
7599
configured_time,
76100
)
77101
)
78-
try:
79-
return view_function(**function_args)
80-
except Exception as exc:
81-
if isinstance(exc, ChaliceViewError):
82-
raise
83-
exc_info = sys.exc_info()
84-
event, hint = event_from_exception(
85-
exc_info,
86-
client_options=client.options,
87-
mechanism={"type": "chalice", "handled": False},
102+
103+
if has_span_streaming_enabled(client.options):
104+
aws_context = app.lambda_context
105+
request_dict = app.current_request.to_dict()
106+
headers = request_dict.get("headers", {}) or {}
107+
108+
header_attrs: "Dict[str, Any]" = {}
109+
for header, value in _filter_headers(
110+
headers, use_annotated_value=False
111+
).items():
112+
header_attrs[f"http.request.header.{header.lower()}"] = value
113+
114+
additional_attrs: "Dict[str, Any]" = {}
115+
if "method" in request_dict:
116+
additional_attrs["http.request.method"] = request_dict["method"]
117+
118+
with sentry_sdk.traces.start_span(
119+
name=aws_context.function_name,
120+
parent_span=None,
121+
attributes={
122+
**_get_lambda_span_attributes(aws_context),
123+
**header_attrs,
124+
**additional_attrs,
125+
},
126+
):
127+
try:
128+
return view_function(**function_args)
129+
except Exception as exc:
130+
if isinstance(exc, ChaliceViewError):
131+
raise
132+
exc_info = sys.exc_info()
133+
sentry_event, hint = event_from_exception(
134+
exc_info,
135+
client_options=client.options,
136+
mechanism={"type": "chalice", "handled": False},
137+
)
138+
sentry_sdk.capture_event(sentry_event, hint=hint)
139+
client.flush()
140+
raise
141+
else:
142+
scope.set_transaction_name(
143+
app.lambda_context.function_name,
144+
source=TransactionSource.COMPONENT,
88145
)
89-
sentry_sdk.capture_event(event, hint=hint)
90-
client.flush()
91-
raise
146+
try:
147+
return view_function(**function_args)
148+
except Exception as exc:
149+
if isinstance(exc, ChaliceViewError):
150+
raise
151+
exc_info = sys.exc_info()
152+
sentry_event, hint = event_from_exception(
153+
exc_info,
154+
client_options=client.options,
155+
mechanism={"type": "chalice", "handled": False},
156+
)
157+
sentry_sdk.capture_event(sentry_event, hint=hint)
158+
client.flush()
159+
raise
92160

93161
return wrapped_view_function # type: ignore
94162

95163

96164
class ChaliceIntegration(Integration):
97165
identifier = "chalice"
166+
origin = f"auto.function.{identifier}"
98167

99168
@staticmethod
100169
def setup_once() -> None:
@@ -129,3 +198,25 @@ def sentry_event_response(
129198
RestAPIEventHandler._get_view_function_response = sentry_event_response
130199
# for everything else (like events)
131200
chalice.app.EventSourceHandler = EventSourceHandler
201+
202+
203+
def _get_lambda_span_attributes(aws_context: "Any") -> "Dict[str, Any]":
204+
invoked_arn = aws_context.invoked_function_arn
205+
split_invoked_arn = invoked_arn.split(":")
206+
aws_region = split_invoked_arn[3] if len(split_invoked_arn) > 3 else "unknown"
207+
208+
return {
209+
"sentry.op": OP.FUNCTION_AWS,
210+
"sentry.origin": ChaliceIntegration.origin,
211+
"sentry.span.source": SegmentSource.COMPONENT,
212+
"cloud.platform": CLOUD_PLATFORM.AWS_LAMBDA,
213+
"cloud.provider": CLOUD_PROVIDER.AWS,
214+
"faas.name": aws_context.function_name,
215+
"cloud.region": aws_region,
216+
"cloud.resource_id": invoked_arn,
217+
"aws.lambda.invoked_arn": invoked_arn,
218+
"faas.invocation_id": aws_context.aws_request_id,
219+
"faas.version": aws_context.function_version,
220+
"aws.log.group.names": [aws_context.log_group_name],
221+
"aws.log.stream.names": [aws_context.log_stream_name],
222+
}

sentry_sdk/integrations/cloud_resource_context.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class CLOUD_PLATFORM: # noqa: N801
4848
"""
4949

5050
AWS_EC2 = "aws_ec2"
51+
AWS_LAMBDA = "aws_lambda"
5152
GCP_COMPUTE_ENGINE = "gcp_compute_engine"
5253

5354

0 commit comments

Comments
 (0)