Skip to content

Commit a6fdebd

Browse files
authored
feat(aws-lambda): Add span streaming support to AWS Lambda integration (#6498)
When span streaming is enabled via the trace_lifecycle experiment, use sentry_sdk.traces.start_span instead of start_transaction so the function span is emitted as a span envelope item with OTel-compatible attributes (cloud.provider, cloud.platform, faas.*, aws.lambda.*). Also adds CLOUD_PLATFORM.AWS_LAMBDA constant and updates the test server helper to route span envelope items into a separate span_items list so tests can assert on streamed spans independently of error envelopes. Depends on getsentry/sentry-conventions#414 being shipped first as it introduces a number of the conventions used here Fixes PY-2307 Fixes #6005
1 parent 213eb58 commit a6fdebd

11 files changed

Lines changed: 356 additions & 23 deletions

File tree

sentry_sdk/integrations/aws_lambda.py

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@
66
from datetime import datetime, timedelta, timezone
77
from os import environ
88
from typing import TYPE_CHECKING
9+
from urllib.parse import urlencode
910

1011
import sentry_sdk
1112
from sentry_sdk.api import continue_trace
1213
from sentry_sdk.consts import OP
1314
from sentry_sdk.integrations import Integration
1415
from sentry_sdk.integrations._wsgi_common import _filter_headers
15-
from sentry_sdk.scope import should_send_default_pii
16+
from sentry_sdk.integrations.cloud_resource_context import (
17+
CLOUD_PLATFORM,
18+
CLOUD_PROVIDER,
19+
)
20+
from sentry_sdk.scope import Scope, should_send_default_pii
21+
from sentry_sdk.traces import SegmentSource
1622
from sentry_sdk.tracing import TransactionSource
23+
from sentry_sdk.tracing_utils import has_span_streaming_enabled
1724
from sentry_sdk.utils import (
1825
AnnotatedValue,
1926
TimeoutThread,
@@ -101,6 +108,7 @@ def sentry_handler(
101108
request_data = {}
102109

103110
configured_time = aws_context.get_remaining_time_in_millis()
111+
aws_region = aws_context.invoked_function_arn.split(":")[3]
104112

105113
with sentry_sdk.isolation_scope() as scope:
106114
timeout_thread = None
@@ -111,9 +119,7 @@ def sentry_handler(
111119
request_data, aws_context, configured_time
112120
)
113121
)
114-
scope.set_tag(
115-
"aws_region", aws_context.invoked_function_arn.split(":")[3]
116-
)
122+
scope.set_tag("aws_region", aws_region)
117123
if batch_size > 1:
118124
scope.set_tag("batch_request", True)
119125
scope.set_tag("batch_size", batch_size)
@@ -144,20 +150,71 @@ def sentry_handler(
144150
if not isinstance(headers, dict):
145151
headers = {}
146152

147-
transaction = continue_trace(
148-
headers,
149-
op=OP.FUNCTION_AWS,
150-
name=aws_context.function_name,
151-
source=TransactionSource.COMPONENT,
152-
origin=AwsLambdaIntegration.origin,
153-
)
154-
with sentry_sdk.start_transaction(
155-
transaction,
156-
custom_sampling_context={
157-
"aws_event": aws_event,
158-
"aws_context": aws_context,
159-
},
160-
):
153+
header_attributes: "dict[str, Any]" = {}
154+
for header, header_value in _filter_headers(
155+
headers, use_annotated_value=False
156+
).items():
157+
header_attributes[f"http.request.header.{header.lower()}"] = (
158+
header_value
159+
)
160+
161+
additional_attributes: "dict[str, Any]" = {}
162+
if "httpMethod" in request_data:
163+
additional_attributes["http.request.method"] = request_data[
164+
"httpMethod"
165+
]
166+
167+
if should_send_default_pii() and "queryStringParameters" in request_data:
168+
qs = request_data["queryStringParameters"]
169+
if qs:
170+
additional_attributes["url.query"] = urlencode(qs)
171+
172+
sampling_context = {
173+
"aws_event": aws_event,
174+
"aws_context": aws_context,
175+
}
176+
177+
function_name = aws_context.function_name
178+
179+
if has_span_streaming_enabled(client.options):
180+
sentry_sdk.traces.continue_trace(headers)
181+
Scope.set_custom_sampling_context(sampling_context)
182+
span_ctx = sentry_sdk.traces.start_span(
183+
name=function_name,
184+
parent_span=None,
185+
attributes={
186+
"sentry.op": OP.FUNCTION_AWS,
187+
"sentry.origin": AwsLambdaIntegration.origin,
188+
"sentry.span.source": SegmentSource.COMPONENT,
189+
"cloud.region": aws_region,
190+
"cloud.resource_id": aws_context.invoked_function_arn,
191+
"cloud.platform": CLOUD_PLATFORM.AWS_LAMBDA,
192+
"cloud.provider": CLOUD_PROVIDER.AWS,
193+
"faas.name": function_name,
194+
"faas.invocation_id": aws_context.aws_request_id,
195+
"faas.version": aws_context.function_version,
196+
"aws.lambda.invoked_arn": aws_context.invoked_function_arn,
197+
"aws.log.group.names": [aws_context.log_group_name],
198+
"aws.log.stream.names": [aws_context.log_stream_name],
199+
"messaging.batch.message_count": batch_size,
200+
**header_attributes,
201+
**additional_attributes,
202+
},
203+
)
204+
else:
205+
transaction = continue_trace(
206+
headers,
207+
op=OP.FUNCTION_AWS,
208+
name=function_name,
209+
source=TransactionSource.COMPONENT,
210+
origin=AwsLambdaIntegration.origin,
211+
)
212+
213+
span_ctx = sentry_sdk.start_transaction(
214+
transaction, custom_sampling_context=sampling_context
215+
)
216+
217+
with span_ctx:
161218
try:
162219
return handler(aws_event, aws_context, *args, **kwargs)
163220
except Exception:

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies
2+
# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry.
3+
4+
# Ignore everything
5+
*
6+
7+
# But not index.py
8+
!index.py
9+
10+
# And not .gitignore itself
11+
!.gitignore
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import os
2+
3+
import sentry_sdk
4+
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
5+
6+
sentry_sdk.init(
7+
dsn=os.environ.get("SENTRY_DSN"),
8+
traces_sample_rate=1.0,
9+
integrations=[AwsLambdaIntegration()],
10+
_experiments={"trace_lifecycle": "stream"},
11+
)
12+
13+
14+
def handler(event, context):
15+
return {"event": event}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies
2+
# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry.
3+
4+
# Ignore everything
5+
*
6+
7+
# But not index.py
8+
!index.py
9+
10+
# And not .gitignore itself
11+
!.gitignore
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import os
2+
3+
import sentry_sdk
4+
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
5+
6+
sentry_sdk.init(
7+
dsn=os.environ.get("SENTRY_DSN"),
8+
traces_sample_rate=1.0,
9+
send_default_pii=True,
10+
integrations=[AwsLambdaIntegration()],
11+
_experiments={"trace_lifecycle": "stream"},
12+
)
13+
14+
15+
def handler(event, context):
16+
return {"event": event}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies
2+
# into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry.
3+
4+
# Ignore everything
5+
*
6+
7+
# But not index.py
8+
!index.py
9+
10+
# And not .gitignore itself
11+
!.gitignore
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import os
2+
3+
import sentry_sdk
4+
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
5+
6+
sentry_sdk.init(
7+
dsn=os.environ.get("SENTRY_DSN"),
8+
traces_sample_rate=1.0,
9+
integrations=[AwsLambdaIntegration()],
10+
_experiments={"trace_lifecycle": "stream"},
11+
)
12+
13+
14+
def handler(event, context):
15+
raise Exception("Oh!")

0 commit comments

Comments
 (0)