diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index cb81d79fc5..8d8ee0d682 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -20,6 +20,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - clickhouse-driver integration: The query is now available under the `db.query.text` span attribute (only if `send_default_pii` is `True`). - `sentry_sdk.init` now returns `None` instead of a context manager. - The `sampling_context` argument of `traces_sampler` now additionally contains all span attributes known at span start. +- The `sampling_context` argument of `traces_sampler` doesn't contain the `asgi_scope` object anymore for ASGI frameworks. Instead, the individual properties, if available, are accessible as `asgi_scope.endpoint`, `asgi_scope.path`, `asgi_scope.root_path`, `asgi_scope.route`, `asgi_scope.scheme`, `asgi_scope.server` and `asgi_scope.type`. ### Removed diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index f67c47ef02..73801ed102 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -209,7 +209,7 @@ async def _run_app(self, scope, receive, send, asgi_version): name=transaction_name, source=transaction_source, origin=self.span_origin, - custom_sampling_context={"asgi_scope": scope}, + attributes=_prepopulate_attributes(scope), ) if should_trace else nullcontext() @@ -324,3 +324,16 @@ def _get_transaction_name_and_source(self, transaction_style, asgi_scope): return name, source return name, source + + +def _prepopulate_attributes(scope): + # type: (Any) -> dict[str, Any] + """Unpack asgi_scope into serializable attributes.""" + scope = scope or {} + + attributes = {} + for attr in ("endpoint", "path", "root_path", "route", "scheme", "server", "type"): + if scope.get(attr): + attributes[f"asgi_scope.{attr}"] = scope[attr] + + return attributes diff --git a/sentry_sdk/integrations/opentelemetry/sampler.py b/sentry_sdk/integrations/opentelemetry/sampler.py index cb722694ac..302b66aaaa 100644 --- a/sentry_sdk/integrations/opentelemetry/sampler.py +++ b/sentry_sdk/integrations/opentelemetry/sampler.py @@ -147,6 +147,7 @@ def should_sample( "transaction_context": { "name": name, "op": attributes.get(SentrySpanAttribute.OP), + "source": attributes.get(SentrySpanAttribute.SOURCE), }, "parent_sampled": get_parent_sampled(parent_span_context, trace_id), } diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index a69a6f98be..a571f3f84c 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1250,6 +1250,7 @@ def __init__( # Prepopulate some attrs so that they're accessible in traces_sampler attributes = attributes or {} attributes[SentrySpanAttribute.OP] = op + attributes[SentrySpanAttribute.SOURCE] = source if sampled is not None: attributes[SentrySpanAttribute.CUSTOM_SAMPLED] = sampled @@ -1260,7 +1261,6 @@ def __init__( self.origin = origin or DEFAULT_SPAN_ORIGIN self.description = description self.name = span_name - self.source = source if status is not None: self.set_status(status) diff --git a/tests/integrations/asgi/test_asgi.py b/tests/integrations/asgi/test_asgi.py index e0a3900a38..fb97c385a0 100644 --- a/tests/integrations/asgi/test_asgi.py +++ b/tests/integrations/asgi/test_asgi.py @@ -721,3 +721,20 @@ async def test_custom_transaction_name( assert transaction_event["type"] == "transaction" assert transaction_event["transaction"] == "foobar" assert transaction_event["transaction_info"] == {"source": "custom"} + + +@pytest.mark.asyncio +async def test_asgi_scope_in_traces_sampler(sentry_init, asgi3_app): + def dummy_traces_sampler(sampling_context): + assert sampling_context["asgi_scope.path"] == "/test" + assert sampling_context["asgi_scope.scheme"] == "http" + + sentry_init( + traces_sampler=dummy_traces_sampler, + traces_sample_rate=1.0, + ) + + app = SentryAsgiMiddleware(asgi3_app) + + async with TestClient(app) as client: + await client.get("/test")