diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 8899cc53b2..648859a233 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -5,6 +5,7 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone from os import environ +from urllib.parse import urlencode import sentry_sdk from sentry_sdk.consts import OP @@ -120,6 +121,9 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs): configured_time = aws_context.get_remaining_time_in_millis() with sentry_sdk.isolation_scope() as scope: + scope.set_transaction_name( + aws_context.function_name, source=TRANSACTION_SOURCE_COMPONENT + ) timeout_thread = None with capture_internal_exceptions(): scope.clear_breadcrumbs() @@ -333,7 +337,7 @@ def event_processor(sentry_event, hint, start_time=start_time): request["url"] = _get_url(aws_event, aws_context) if "queryStringParameters" in aws_event: - request["query_string"] = aws_event["queryStringParameters"] + request["query_string"] = urlencode(aws_event["queryStringParameters"]) if "headers" in aws_event: request["headers"] = _filter_headers(aws_event["headers"]) @@ -373,7 +377,9 @@ def _get_url(aws_event, aws_context): path = aws_event.get("path", None) headers = aws_event.get("headers") - if headers is None: + # Some AWS Services (ie. EventBridge) set headers as a list + # or None, so we must ensure it is a dict + if not isinstance(headers, dict): headers = {} host = headers.get("Host", None) @@ -478,7 +484,10 @@ def _prepopulate_attributes(aws_event, aws_context): for prop, attr in EVENT_TO_ATTRIBUTES.items(): if aws_event.get(prop) is not None: - attributes[attr] = aws_event[prop] + if prop == "queryStringParameters": + attributes[attr] = urlencode(aws_event[prop]) + else: + attributes[attr] = aws_event[prop] for prop, attr in CONTEXT_TO_ATTRIBUTES.items(): if getattr(aws_context, prop, None) is not None: @@ -487,7 +496,7 @@ def _prepopulate_attributes(aws_event, aws_context): url = _get_url(aws_event, aws_context) if url: if aws_event.get("queryStringParameters"): - url += f"?{aws_event['queryStringParameters']}" + url += f"?{urlencode(aws_event['queryStringParameters'])}" attributes["url.full"] = url headers = {} diff --git a/tests/integrations/aws_lambda/test_aws.py b/tests/integrations/aws_lambda/test_aws.py index e58fab292d..822a7a9146 100644 --- a/tests/integrations/aws_lambda/test_aws.py +++ b/tests/integrations/aws_lambda/test_aws.py @@ -67,6 +67,8 @@ def truncate_data(data): if data["contexts"].get("trace") is not None: cleaned_data["contexts"]["trace"] = data["contexts"].get("trace") + if cleaned_data["contexts"]["trace"].get("data", {}) != {}: + cleaned_data["contexts"]["trace"]["data"] = {"removed": "by truncate_data()"} if data.get("transaction") is not None: cleaned_data["transaction"] = data.get("transaction") @@ -287,7 +289,8 @@ def test_handler(event, context): "X-Forwarded-Proto": "https" }, "queryStringParameters": { - "bonkers": "true" + "bonkers": "true", + "wild": "false" }, "pathParameters": null, "stageVariables": null, @@ -312,7 +315,7 @@ def test_handler(event, context): "X-Forwarded-Proto": "https", }, "method": "GET", - "query_string": {"bonkers": "true"}, + "query_string": "bonkers=true&wild=false", "url": "https://iwsz2c7uwi.execute-api.us-east-1.amazonaws.com/asd", } @@ -487,6 +490,15 @@ def test_handler(event, context): ), (b"[]", False, 1), ], + ids=[ + "int", + "float", + "string", + "bool", + "list", + "list_with_request_data", + "empty_list", + ], ) def test_non_dict_event( run_lambda_function, @@ -539,9 +551,7 @@ def test_handler(event, context): "headers": {"Host": "x1.io", "X-Forwarded-Proto": "https"}, "method": "GET", "url": "https://x1.io/1", - "query_string": { - "done": "f", - }, + "query_string": "done=f", } else: request_data = {"url": "awslambda:///{}".format(function_name)} @@ -590,7 +600,7 @@ def test_traces_sampler_gets_correct_values_in_sampling_context( import inspect - _, response = run_lambda_function( + function_code = ( LAMBDA_PRELUDE + dedent(inspect.getsource(StringContaining)) + dedent(inspect.getsource(DictionaryContaining)) @@ -621,7 +631,7 @@ def test_handler(event, context): { "http.request.method": "GET", "url.path": "/sit/stay/rollover", - "url.query": "repeat=again", + "url.query": "repeat=twice", "url.full": "http://x.io/sit/stay/rollover?repeat=twice", "network.protocol.name": "http", "server.address": "x.io", @@ -643,10 +653,15 @@ def test_handler(event, context): traces_sampler=traces_sampler, ) """ - ), - b'{"httpMethod": "GET", "path": "/sit/stay/rollover", "query_string": {"repeat": "again"}, "headers": {"Host": "x.io", "X-Forwarded-Proto": "http", "Custom-Header": "Custom Value"}}', + ) ) + payload = b'{"httpMethod": "GET", "path": "/sit/stay/rollover", "queryStringParameters": {"repeat": "twice"}, "headers": {"Host": "x.io", "X-Forwarded-Proto": "http", "Custom-Header": "Custom Value"}}' + + _, response = run_lambda_function( + code=function_code, + payload=payload, + ) assert response["Payload"]["AssertionError raised"] is False