Skip to content

Commit 1e758fa

Browse files
authored
fix(aws-lambda): Attach user info when streaming spans (#7429)
Add user info onto streamed spans since they don't go through the event processor.
1 parent ffac8f2 commit 1e758fa

4 files changed

Lines changed: 156 additions & 26 deletions

File tree

‎sentry_sdk/integrations/aws_lambda.py‎

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,27 @@
4545
MILLIS_TO_SECONDS = 1000.0
4646

4747

48+
def _get_user_from_event(aws_event: "dict[str, Any]") -> "dict[str, Any]":
49+
if not isinstance(aws_event, dict):
50+
return {}
51+
52+
identity = aws_event.get("requestContext", {}).get("identity")
53+
if identity is None:
54+
return {}
55+
56+
user_info: "dict[str, Any]" = {}
57+
58+
user_arn = identity.get("userArn")
59+
if user_arn is not None:
60+
user_info["id"] = user_arn
61+
62+
ip = identity.get("sourceIp")
63+
if ip is not None:
64+
user_info["ip_address"] = ip
65+
66+
return user_info
67+
68+
4869
def _wrap_init_error(init_error: "F") -> "F":
4970
@ensure_integration_enabled(AwsLambdaIntegration, init_error)
5071
def sentry_init_error(*args: "Any", **kwargs: "Any") -> "Any":
@@ -181,6 +202,17 @@ def sentry_handler(
181202
elif should_send_default_pii():
182203
additional_attributes["url.query"] = urlencode(qs)
183204

205+
if not scope._user:
206+
if has_data_collection_enabled(client.options):
207+
if client.options["data_collection"]["user_info"]:
208+
user_info = _get_user_from_event(request_data)
209+
if user_info:
210+
scope.set_user(user_info)
211+
elif should_send_default_pii():
212+
user_info = _get_user_from_event(request_data)
213+
if user_info:
214+
scope.set_user(user_info)
215+
184216
sampling_context = {
185217
"aws_event": aws_event,
186218
"aws_context": aws_context,
@@ -440,38 +472,22 @@ def event_processor(
440472
client_options = sentry_sdk.get_client().options
441473
if has_data_collection_enabled(client_options):
442474
if client_options["data_collection"]["user_info"]:
443-
user_info = sentry_event.setdefault("user", {})
444-
445-
identity = aws_event.get("requestContext", {}).get("identity")
446-
if identity is None:
447-
identity = {}
448-
449-
id = identity.get("userArn")
450-
if id is not None:
451-
user_info.setdefault("id", id)
452-
453-
ip = identity.get("sourceIp")
454-
if ip is not None:
455-
user_info.setdefault("ip_address", ip)
475+
extracted_user = _get_user_from_event(aws_event)
476+
if extracted_user:
477+
user_info = sentry_event.setdefault("user", {})
478+
for key, value in extracted_user.items():
479+
user_info.setdefault(key, value)
456480

457481
if "incoming_request" in client_options["data_collection"]["http_bodies"]:
458482
if "body" in aws_event:
459483
request["data"] = aws_event.get("body", "")
460484

461485
elif should_send_default_pii():
462-
user_info = sentry_event.setdefault("user", {})
463-
464-
identity = aws_event.get("requestContext", {}).get("identity")
465-
if identity is None:
466-
identity = {}
467-
468-
id = identity.get("userArn")
469-
if id is not None:
470-
user_info.setdefault("id", id)
471-
472-
ip = identity.get("sourceIp")
473-
if ip is not None:
474-
user_info.setdefault("ip_address", ip)
486+
extracted_user = _get_user_from_event(aws_event)
487+
if extracted_user:
488+
user_info = sentry_event.setdefault("user", {})
489+
for key, value in extracted_user.items():
490+
user_info.setdefault(key, value)
475491

476492
if "body" in aws_event:
477493
request["data"] = aws_event.get("body", "")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
trace_lifecycle="stream",
11+
_experiments={
12+
"data_collection": {
13+
"user_info": False,
14+
}
15+
},
16+
)
17+
18+
19+
def handler(event, context):
20+
return {"event": event}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
trace_lifecycle="stream",
11+
_experiments={
12+
"data_collection": {
13+
"user_info": True,
14+
}
15+
},
16+
)
17+
18+
19+
def handler(event, context):
20+
return {"event": event}

‎tests/integrations/aws_lambda/test_aws_lambda.py‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,6 +1144,80 @@ def test_span_streaming_url_query_params_with_data_collection(
11441144
)
11451145

11461146

1147+
def test_span_streaming_user_info_with_send_default_pii(
1148+
lambda_client, test_environment
1149+
):
1150+
payload = b"""
1151+
{
1152+
"resource": "/asd",
1153+
"path": "/asd",
1154+
"httpMethod": "GET",
1155+
"headers": {
1156+
"Host": "iwsz2c7uwi.execute-api.us-east-1.amazonaws.com",
1157+
"User-Agent": "custom",
1158+
"X-Forwarded-Proto": "https"
1159+
},
1160+
"queryStringParameters": {
1161+
"bonkers": "true"
1162+
},
1163+
"pathParameters": null,
1164+
"stageVariables": null,
1165+
"requestContext": {
1166+
"identity": {
1167+
"sourceIp": "213.47.147.207",
1168+
"userArn": "42"
1169+
}
1170+
},
1171+
"body": null,
1172+
"isBase64Encoded": false
1173+
}
1174+
"""
1175+
1176+
lambda_client.invoke(
1177+
FunctionName="BasicOkSpanStreamingPii",
1178+
Payload=payload,
1179+
)
1180+
span_items = test_environment["server"].span_items
1181+
1182+
segment_spans = [s for s in span_items if s.get("is_segment")]
1183+
assert len(segment_spans) == 1
1184+
attrs = segment_spans[0]["attributes"]
1185+
1186+
assert _get_span_attr(attrs, "user.id") == "42"
1187+
1188+
1189+
def test_span_streaming_user_info_with_data_collection_user_info_on(
1190+
lambda_client, test_environment
1191+
):
1192+
lambda_client.invoke(
1193+
FunctionName="BasicOkSpanStreamingDataCollectionUserInfoOn",
1194+
Payload=USER_INFO_PAYLOAD,
1195+
)
1196+
span_items = test_environment["server"].span_items
1197+
1198+
segment_spans = [s for s in span_items if s.get("is_segment")]
1199+
assert len(segment_spans) == 1
1200+
attrs = segment_spans[0]["attributes"]
1201+
1202+
assert _get_span_attr(attrs, "user.id") == "42"
1203+
1204+
1205+
def test_span_streaming_user_info_with_data_collection_user_info_off(
1206+
lambda_client, test_environment
1207+
):
1208+
lambda_client.invoke(
1209+
FunctionName="BasicOkSpanStreamingDataCollectionUserInfoOff",
1210+
Payload=USER_INFO_PAYLOAD,
1211+
)
1212+
span_items = test_environment["server"].span_items
1213+
1214+
segment_spans = [s for s in span_items if s.get("is_segment")]
1215+
assert len(segment_spans) == 1
1216+
attrs = segment_spans[0]["attributes"]
1217+
1218+
assert "user.id" not in attrs
1219+
1220+
11471221
@pytest.mark.parametrize(
11481222
"lambda_function_name",
11491223
["RaiseErrorPerformanceEnabled", "RaiseErrorPerformanceDisabled"],

0 commit comments

Comments
 (0)