From 46ed68d43d4a57fa06dc7d4457a7f073f1d4c3ad Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 07:10:32 +0000 Subject: [PATCH 1/4] fix(aws-lambda): Only set messaging.batch.message_count for SQS and SNS batches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lambda span set messaging.batch.message_count to 1 for every non-list event and to len(event) for a top-level list. An SQS or SNS delivery is a dict with Records, so a batch of 10 messages reported 1, and HTTP or direct invocations reported a batch they don't have. Set it only when all records come from SQS or SNS, to the number of records. Co-Authored-By: Johannes Daxböck <114897+dingsdax@users.noreply.github.com> --- .../integrations/aws_lambda/_handler.py | 34 +++++++++++++-- .../aws_lambda/test_aws_lambda.py | 41 ++++++++++++++++++- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index 89ba0089b7..735551fef3 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -29,7 +29,7 @@ ) if TYPE_CHECKING: - from typing import Any, Callable, Mapping, TypeVar + from typing import Any, Callable, Mapping, Optional, TypeVar from sentry_sdk._types import Attributes @@ -38,6 +38,30 @@ TIMEOUT_WARNING_BUFFER = 1500 MILLIS_TO_SECONDS = 1000.0 +# `eventSource` of SQS records, `EventSource` of SNS records. +MESSAGING_EVENT_SOURCES = ("aws:sqs", "aws:sns") + + +def _get_messaging_batch_size(aws_event: "Any") -> "Optional[int]": + """The number of SQS or SNS messages in the event, or `None` for any other event.""" + if not isinstance(aws_event, dict): + return None + + records = aws_event.get("Records") + if not isinstance(records, list) or not records: + return None + + sources = { + record.get("eventSource") or record.get("EventSource") + if isinstance(record, dict) + else None + for record in records + } + if len(sources) != 1 or sources.pop() not in MESSAGING_EVENT_SOURCES: + return None + + return len(records) + def _wrap_handler(handler: "F") -> "F": from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration @@ -64,10 +88,8 @@ def sentry_handler( if isinstance(aws_event, list) and len(aws_event) >= 1: request_data = aws_event[0] - batch_size = len(aws_event) else: request_data = aws_event - batch_size = 1 if not isinstance(request_data, dict): # If we're not dealing with a dictionary, we won't be able to get @@ -160,11 +182,15 @@ def sentry_handler( SPANDATA.SENTRY_SEGMENT_NAME_SOURCE: SegmentNameSource.COMPONENT, SPANDATA.CLOUD_PROVIDER: CLOUD_PROVIDER, SPANDATA.CLOUD_PLATFORM: CLOUD_PLATFORM, - SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT: batch_size, **header_attributes, **additional_attributes, } + with capture_internal_exceptions(): + batch_size = _get_messaging_batch_size(aws_event) + if batch_size is not None: + attributes[SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT] = batch_size + with capture_internal_exceptions(): invoked_function_arn = aws_context.invoked_function_arn function_version = aws_context.function_version diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index 2d7712b2d2..d039f6f956 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -756,7 +756,7 @@ def test_no_error(lambda_client, test_environment): assert _get_span_attr(attrs, SPANDATA.AWS_LOG_STREAM_NAMES) == [ LATEST_FUNCTION_VERSION ] - assert _get_span_attr(attrs, SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT) == 1 + assert SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT not in attrs def test_error(lambda_client, test_environment): @@ -810,7 +810,44 @@ def test_error(lambda_client, test_environment): assert _get_span_attr(attrs, SPANDATA.AWS_LOG_STREAM_NAMES) == [ LATEST_FUNCTION_VERSION ] - assert _get_span_attr(attrs, SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT) == 1 + assert SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT not in attrs + + +_SQS_RECORD = {"eventSource": "aws:sqs"} +_SNS_RECORD = {"EventSource": "aws:sns"} + + +@pytest.mark.parametrize( + "aws_event,batch_count", + [ + pytest.param({"Records": [_SQS_RECORD, _SQS_RECORD]}, 2, id="sqs"), + pytest.param({"Records": [_SNS_RECORD]}, 1, id="sns"), + pytest.param({"Records": [{"eventSource": "aws:s3"}]}, None, id="s3"), + pytest.param({"Records": [_SQS_RECORD, _SNS_RECORD]}, None, id="mixed"), + pytest.param({"httpMethod": "GET", "path": "/"}, None, id="http"), + pytest.param( + [{"good dog": "Maisey"}, {"good dog": "Charlie"}], None, id="list" + ), + ], +) +def test_messaging_batch_message_count( + lambda_client, test_environment, aws_event, batch_count +): + lambda_client.invoke( + FunctionName="BasicOkSpanStreaming", + Payload=json.dumps(aws_event), + ) + + (segment_span,) = [ + s for s in test_environment["server"].span_items if s["is_segment"] + ] + attrs = segment_span["attributes"] + if batch_count is None: + assert SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT not in attrs + else: + assert ( + _get_span_attr(attrs, SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT) == batch_count + ) def test_trace_continuation(lambda_client, test_environment): From 9337a81c30dd0a475c05e0c50b4dce36fc4813cf Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 07:55:45 +0000 Subject: [PATCH 2/4] fix(aws-lambda): Remove messaging.batch.message_count from the Lambda span Replace the SQS/SNS record counting with a plain removal. The count belongs on messaging spans (boto3 receive and batch-send spans, and the queue.process span for SQS/SNS triggers), as in the OTel messaging conventions. The JS SDK doesn't set it on the Lambda span either. The attribute was added in 3.0 and never shipped in 2.x. Co-Authored-By: johdax <114897+dingsdax@users.noreply.github.com> --- .../integrations/aws_lambda/_handler.py | 31 +--------------- .../aws_lambda/test_aws_lambda.py | 37 ------------------- 2 files changed, 1 insertion(+), 67 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda/_handler.py b/sentry_sdk/integrations/aws_lambda/_handler.py index 735551fef3..7a825bed28 100644 --- a/sentry_sdk/integrations/aws_lambda/_handler.py +++ b/sentry_sdk/integrations/aws_lambda/_handler.py @@ -29,7 +29,7 @@ ) if TYPE_CHECKING: - from typing import Any, Callable, Mapping, Optional, TypeVar + from typing import Any, Callable, Mapping, TypeVar from sentry_sdk._types import Attributes @@ -38,30 +38,6 @@ TIMEOUT_WARNING_BUFFER = 1500 MILLIS_TO_SECONDS = 1000.0 -# `eventSource` of SQS records, `EventSource` of SNS records. -MESSAGING_EVENT_SOURCES = ("aws:sqs", "aws:sns") - - -def _get_messaging_batch_size(aws_event: "Any") -> "Optional[int]": - """The number of SQS or SNS messages in the event, or `None` for any other event.""" - if not isinstance(aws_event, dict): - return None - - records = aws_event.get("Records") - if not isinstance(records, list) or not records: - return None - - sources = { - record.get("eventSource") or record.get("EventSource") - if isinstance(record, dict) - else None - for record in records - } - if len(sources) != 1 or sources.pop() not in MESSAGING_EVENT_SOURCES: - return None - - return len(records) - def _wrap_handler(handler: "F") -> "F": from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration @@ -186,11 +162,6 @@ def sentry_handler( **additional_attributes, } - with capture_internal_exceptions(): - batch_size = _get_messaging_batch_size(aws_event) - if batch_size is not None: - attributes[SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT] = batch_size - with capture_internal_exceptions(): invoked_function_arn = aws_context.invoked_function_arn function_version = aws_context.function_version diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index d039f6f956..37dfbcac50 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -813,43 +813,6 @@ def test_error(lambda_client, test_environment): assert SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT not in attrs -_SQS_RECORD = {"eventSource": "aws:sqs"} -_SNS_RECORD = {"EventSource": "aws:sns"} - - -@pytest.mark.parametrize( - "aws_event,batch_count", - [ - pytest.param({"Records": [_SQS_RECORD, _SQS_RECORD]}, 2, id="sqs"), - pytest.param({"Records": [_SNS_RECORD]}, 1, id="sns"), - pytest.param({"Records": [{"eventSource": "aws:s3"}]}, None, id="s3"), - pytest.param({"Records": [_SQS_RECORD, _SNS_RECORD]}, None, id="mixed"), - pytest.param({"httpMethod": "GET", "path": "/"}, None, id="http"), - pytest.param( - [{"good dog": "Maisey"}, {"good dog": "Charlie"}], None, id="list" - ), - ], -) -def test_messaging_batch_message_count( - lambda_client, test_environment, aws_event, batch_count -): - lambda_client.invoke( - FunctionName="BasicOkSpanStreaming", - Payload=json.dumps(aws_event), - ) - - (segment_span,) = [ - s for s in test_environment["server"].span_items if s["is_segment"] - ] - attrs = segment_span["attributes"] - if batch_count is None: - assert SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT not in attrs - else: - assert ( - _get_span_attr(attrs, SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT) == batch_count - ) - - def test_trace_continuation(lambda_client, test_environment): trace_id = "471a43a4192642f0b136d5159a501701" parent_span_id = "6e8f22c393e68f19" From 1c106688df3edd0644472fa57121235cc65323ff Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 08:28:56 +0000 Subject: [PATCH 3/4] ci: Trigger CI (the previous push started no workflows) From c42e110e3f1768f0557ecf4824550aac692c4435 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 12:19:33 +0000 Subject: [PATCH 4/4] test(aws-lambda): Drop the unused batch_size column in test_non_dict_event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Johannes Daxböck <114897+dingsdax@users.noreply.github.com> --- .../integrations/aws_lambda/test_aws_lambda.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/integrations/aws_lambda/test_aws_lambda.py b/tests/integrations/aws_lambda/test_aws_lambda.py index 37dfbcac50..501d6096ba 100644 --- a/tests/integrations/aws_lambda/test_aws_lambda.py +++ b/tests/integrations/aws_lambda/test_aws_lambda.py @@ -159,12 +159,12 @@ def test_timeout_error_scope_modified(lambda_client, test_environment): @pytest.mark.parametrize( - "aws_event, has_request_data, batch_size", + "aws_event, has_request_data", [ - (b"1231", False, 1), - (b"11.21", False, 1), - (b'"Good dog!"', False, 1), - (b"true", False, 1), + (b"1231", False), + (b"11.21", False), + (b'"Good dog!"', False), + (b"true", False), ( b""" [ @@ -175,7 +175,6 @@ def test_timeout_error_scope_modified(lambda_client, test_environment): ] """, False, - 4, ), ( b""" @@ -207,9 +206,8 @@ def test_timeout_error_scope_modified(lambda_client, test_environment): ] """, True, - 2, ), - (b"[]", False, 1), + (b"[]", False), ], ids=[ "event as integer", @@ -221,9 +219,7 @@ def test_timeout_error_scope_modified(lambda_client, test_environment): "event as empty list", ], ) -def test_non_dict_event( - lambda_client, test_environment, aws_event, has_request_data, batch_size -): +def test_non_dict_event(lambda_client, test_environment, aws_event, has_request_data): lambda_client.invoke( FunctionName="BasicException", Payload=aws_event,