Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
70dca2a
.
pabloDeputter Oct 26, 2026
0f90423
wip
pabloDeputter Oct 27, 2026
053099c
.
pabloDeputter Oct 26, 2026
c73d9ba
fix(boto3): Add `capture_internal_exceptions()` guard to `get_span_na…
pabloDeputter Oct 28, 2026
61994bf
fix(boto3): Simplify error type handling in batch operations
pabloDeputter Oct 28, 2026
6ccbcd8
wip
pabloDeputter Oct 28, 2026
9235dd0
Merge branch 'major/3.0' into pablo/boto3-add-sqs-extension
pabloDeputter Oct 28, 2026
be20b35
Merge branch 'pablo/boto3-add-sqs-extension' into pablo/boto3-add-sns…
pabloDeputter Oct 28, 2026
50a49d1
add "publish_batch" method requirement
pabloDeputter Oct 28, 2026
4738824
fix
pabloDeputter Oct 28, 2026
c640799
fix(boto3): make sure span is sent
pabloDeputter Oct 30, 2026
f6c8099
Merge remote-tracking branch 'origin/major/3.0' into pablo/boto3-add-…
sentry-junior[bot] Nov 9, 2026
0cb573d
Merge branch 'pablo/boto3-add-sqs-extension' into pablo/boto3-add-sns…
sentry-junior[bot] Nov 9, 2026
48eae1c
ref(boto3): Guard SQS ARN and batch-count reads separately
sentry-junior[bot] Nov 9, 2026
0e58846
Merge branch 'pablo/boto3-add-sqs-extension' into pablo/boto3-add-sns…
sentry-junior[bot] Nov 9, 2026
54710b7
ref(boto3): Share the batch error.type code with SQS
sentry-junior[bot] Nov 9, 2026
438ded1
merge major branch
alexander-alderman-webb Nov 10, 2026
6588b5b
merge
alexander-alderman-webb Nov 10, 2026
4d8e8f5
Merge branch 'major/3.0' into pablo/boto3-add-sns-extension
sentry-junior[bot] Nov 11, 2026
e53db10
fix(boto3): Guard SNS response attributes
sentry-junior[bot] Nov 11, 2026
ae87c83
fix(boto3): Guard SNS request attribute reads
sentry-junior[bot] Nov 11, 2026
a7efd55
test(boto3): Skip SNS invalid-params test when the operation is missing
sentry-junior[bot] Nov 11, 2026
165dcf3
docs(boto3): Document SNS span op and name changes in migration guide
sentry-junior[bot] Nov 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh
- The `RustTracingIntegration` no longer includes the values of tracing fields if `send_default_pii` is enabled. Pass `include_tracing_fields=True` to the integration to include them.
- Whether the Dramatiq integration attaches message bodies to events is now controlled by `data_collection`'s `queues` setting instead of `http_bodies`.
- The Celery integration no longer sets the status of task spans to error when a task raises a control flow exception (`Retry`, `Ignore`, or `Reject`).
- The boto3 integration now names spans `<ServiceID>.<Operation>` (for example, `S3.GetObject` or `DynamoDB.GetItem`), following OpenTelemetry semantic conventions. Previously, spans were named `aws.<service-id>.<Operation>` (for example, `aws.s3.GetObject`). Its spans, including the span for reading a streamed response body, now also have the origin `auto.aws.boto3` instead of `auto.http.boto3`. SQS messaging calls are the exception: `SendMessage` and `SendMessageBatch` are named `send <queue name>`, `ReceiveMessage` is named `receive <queue name>`, and `DeleteMessage` and `DeleteMessageBatch` are named `settle <queue name>`. Update any alerts, dashboards, or `ignore_spans` rules that match on the old names or origin.
- The boto3 integration now names spans `<ServiceID>.<Operation>` (for example, `S3.GetObject` or `DynamoDB.GetItem`), following OpenTelemetry semantic conventions. Previously, spans were named `aws.<service-id>.<Operation>` (for example, `aws.s3.GetObject`). Its spans, including the span for reading a streamed response body, now also have the origin `auto.aws.boto3` instead of `auto.http.boto3`. SQS and SNS messaging calls are the exception: SQS `SendMessage` and `SendMessageBatch` are named `send <queue name>`, `ReceiveMessage` is named `receive <queue name>`, and `DeleteMessage` and `DeleteMessageBatch` are named `settle <queue name>`; SNS `Publish` and `PublishBatch` are named `send <topic name>`. Update any alerts, dashboards, or `ignore_spans` rules that match on the old names or origin.
- Enabling an AI agent integration (LangChain, OpenAI Agents, or Pydantic AI) no longer disables the OpenAI, Anthropic, and Google GenAI integrations. Instead, those integrations don't create chat spans for requests made from within an agent framework's chat span, but still create other spans, such as embedding spans. The LangChain integration no longer instruments embeddings directly; embedding spans are now created by the integration of the underlying client library.
- Whether the MCP integration records tool arguments, tool results, and prompt contents is now controlled by `data_collection`'s new `mcp` setting (`{"inputs": bool, "outputs": bool}`, both `True` by default) instead of the `gen_ai` setting.
- With `handled_tool_call_exceptions=True` (the default), the Pydantic AI integration now reports an error for every failed tool argument validation attempt, including retries. Previously, only one error was reported per tool call.
Expand Down Expand Up @@ -77,7 +77,7 @@ Span ops are now a smaller, framework-neutral set.
- The Django integration now emits `view.render` instead of `view.response.render`, `view` instead of `view.authenticate`, and `function` instead of `event.django`.
- The Django, Starlette, Starlite, and Litestar integrations now emit `middleware` instead of `middleware.django`, `middleware.starlette`, `middleware.starlette.receive`, `middleware.starlette.send`, `middleware.starlite`, `middleware.starlite.receive`, `middleware.starlite.send`, `middleware.litestar`, `middleware.litestar.receive`, and `middleware.litestar.send`.
- The ARQ, Celery, Django, Dramatiq, Huey, RQ, and Ray integrations now emit `queue.publish` and `queue.process` instead of `queue.submit.arq`, `queue.task.arq`, `queue.submit.celery`, `queue.task.celery`, `queue.submit.django`, `queue.task.dramatiq`, `queue.submit.huey`, `queue.task.huey`, `queue.task.rq`, `queue.submit.ray`, and `queue.task.ray`.
- The Boto3 integration now emits `rpc` instead of `http.client` for AWS API calls. SQS messaging calls emit `queue.publish` (`SendMessage`, `SendMessageBatch`), `queue.receive` (`ReceiveMessage`), and `queue.settle` (`DeleteMessage`, `DeleteMessageBatch`) instead.
- The Boto3 integration now emits `rpc` instead of `http.client` for AWS API calls. SQS messaging calls emit `queue.publish` (`SendMessage`, `SendMessageBatch`), `queue.receive` (`ReceiveMessage`), and `queue.settle` (`DeleteMessage`, `DeleteMessageBatch`) instead, and SNS `Publish` and `PublishBatch` emit `queue.publish`.

#### GenAI span attributes

Expand Down
6 changes: 6 additions & 0 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,12 @@ class SPANDATA:
Example: "https://sqs.us-east-1.amazonaws.com/123456789012/orders"
"""

AWS_SNS_TOPIC_ARN = "aws.sns.topic.arn"
"""
The ARN of the AWS SNS topic the request refers to.
Example: "arn:aws:sns:us-east-1:123456789012:alerts"
"""

CACHE_HIT = "cache.hit"
"""
A boolean indicating whether the requested data was found in the cache.
Expand Down
119 changes: 107 additions & 12 deletions sentry_sdk/integrations/boto3/_services/sns.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,131 @@
from typing import TYPE_CHECKING

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations.boto3._services.base import _ServiceExtension
from sentry_sdk.integrations.boto3._utils import _get_aws_arn_attributes
from sentry_sdk.integrations.boto3._utils import (
_get_aws_arn_attributes,
_get_batch_error_type,
)
from sentry_sdk.traces import SpanStatus
from sentry_sdk.utils import capture_internal_exceptions

if TYPE_CHECKING:
from typing import Any
from typing import Any, Optional

from sentry_sdk._types import Attributes
from sentry_sdk.integrations.boto3._context import AwsCallContext
from sentry_sdk.traces import Span

_PUBLISH_OPERATIONS = ("Publish", "PublishBatch")


class _SNSExtension(_ServiceExtension):
def get_span_op(self, ctx: "AwsCallContext") -> "Optional[str]":
if ctx.operation_name in _PUBLISH_OPERATIONS:
return OP.QUEUE_PUBLISH
# defaults to `rpc` for other operations.
return None

def get_span_name(
self, ctx: "AwsCallContext", attributes: "Attributes"
) -> "Optional[str]":
if ctx.operation_name not in _PUBLISH_OPERATIONS:
return None

destination = attributes.get(SPANDATA.MESSAGING_DESTINATION_NAME)
if destination is None and SPANDATA.SERVER_ADDRESS in attributes:
destination = f"{attributes[SPANDATA.SERVER_ADDRESS]}:{attributes[SPANDATA.SERVER_PORT]}"

# for example: `send <topic name>` when `messaging.operation.type=send` (always send).
# https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/#span-name
return f"send {destination}" if destination else "send"
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved

def get_request_attributes(self, ctx: "AwsCallContext") -> "Attributes":
attributes: "Attributes" = {}

# botocore validates params after this hook, so a malformed value must only
# lose the attributes read from it, not the span.
target_arn = ctx.params.get("TargetArn")
endpoint_arn = ctx.params.get("EndpointArn")
topic_arn = None
with capture_internal_exceptions():
# `TargetArn` can be either a topic or a platform endpoint; a topic ARN
# should never contain ":endpoint/".
if target_arn and ":endpoint/" in target_arn:
endpoint_arn, target_arn = target_arn, None

# `TopicArn` and `ResourceArn` are always topic ARNs.
topic_arn = (
ctx.params.get("TopicArn")
or ctx.params.get("ResourceArn")
or target_arn
)

if topic_arn:
attributes[SPANDATA.AWS_SNS_TOPIC_ARN] = topic_arn
# `arn:aws:sns:eu-north-1:123456789012:alerts` -> `alerts`
topic_name = topic_arn.split(":")[-1]
attributes[SPANDATA.MESSAGING_DESTINATION_NAME] = topic_name
elif ctx.operation_name == "CreateTopic" and "Name" in ctx.params:
attributes[SPANDATA.MESSAGING_DESTINATION_NAME] = ctx.params["Name"]

if ctx.operation_name in _PUBLISH_OPERATIONS:
attributes.update(
_get_aws_arn_attributes(
(
ctx.params.get("EndpointArn"),
ctx.params.get("PlatformApplicationArn"),
ctx.params.get("SubscriptionArn"),
ctx.params.get("TargetArn"),
ctx.params.get("TopicArn"),
),
("sns",),
)
{
SPANDATA.SENTRY_KIND: "producer",
SPANDATA.MESSAGING_SYSTEM: "aws.sns",
SPANDATA.MESSAGING_OPERATION_TYPE: "send",
SPANDATA.MESSAGING_OPERATION_NAME: "publish",
}
)

# phone numbers and device endpoints are PII.
if "PhoneNumber" in ctx.params or endpoint_arn:
attributes[SPANDATA.MESSAGING_DESTINATION_NAME] = "[Filtered]"
if "PublishBatchRequestEntries" in ctx.params:
with capture_internal_exceptions():
attributes[SPANDATA.MESSAGING_BATCH_MESSAGE_COUNT] = len(
ctx.params["PublishBatchRequestEntries"]
)

# if scrubbing fails, drop all ARN attributes rather than send a raw
# endpoint or subscription ARN.
with capture_internal_exceptions():
# endpoints and subscriptions identify a single device or subscriber.
subscription_arn = ctx.params.get("SubscriptionArn")
if not sentry_sdk.get_client().options["data_collection"]["user_info"]:
if endpoint_arn:
endpoint_arn = endpoint_arn.rsplit("/", 1)[0].replace(
":endpoint/", ":app/", 1
)
if subscription_arn:
subscription_arn = subscription_arn.rsplit(":", 1)[0]

arns = (
ctx.params.get("PlatformApplicationArn"),
topic_arn,
endpoint_arn,
subscription_arn,
)
attributes.update(_get_aws_arn_attributes(arns, ("sns",)))
return attributes

def on_success_response(
self, ctx: "AwsCallContext", response: "Any", span: "Span"
) -> None:
attributes: "Attributes" = {}

# the call already succeeded; an unexpected response shape, e.g. from a
# `Stubber` or an SNS-compatible service, must only lose these attributes.
with capture_internal_exceptions():
if ctx.operation_name == "Publish" and "MessageId" in response:
attributes[SPANDATA.MESSAGING_MESSAGE_ID] = response["MessageId"]

error_type = _get_batch_error_type(response)
if error_type is not None:
span.status = SpanStatus.ERROR
attributes[SPANDATA.ERROR_TYPE] = error_type

span.set_attributes(attributes)
span.end()
Loading
Loading