Skip to content

Commit 282ba35

Browse files
committed
feat(asgi): Migrate away from event processor in span first
1 parent 2604409 commit 282ba35

File tree

2 files changed

+91
-4
lines changed

2 files changed

+91
-4
lines changed

sentry_sdk/integrations/_asgi_common.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from typing import Union
1313
from typing_extensions import Literal
1414

15+
from sentry_sdk._types import Attributes
1516
from sentry_sdk.utils import AnnotatedValue
1617

1718

@@ -105,3 +106,32 @@ def _get_request_data(asgi_scope: "Any") -> "Dict[str, Any]":
105106
request_data["env"] = {"REMOTE_ADDR": _get_ip(asgi_scope)}
106107

107108
return request_data
109+
110+
111+
def _get_request_attributes(asgi_scope: "Any") -> "dict[str, Any]":
112+
"""
113+
Return attributes related to the HTTP request from the ASGI scope.
114+
"""
115+
attributes: "Attributes" = {}
116+
117+
ty = asgi_scope["type"]
118+
if ty in ("http", "websocket"):
119+
if asgi_scope.get("method"):
120+
attributes["http.request.method"] = asgi_scope["method"].upper()
121+
122+
headers = _filter_headers(_get_headers(asgi_scope))
123+
# TODO[span-first]: Correctly merge headers if duplicate
124+
for header, value in headers.items():
125+
attributes[f"http.request.headers.{header.lower()}"] = [value]
126+
127+
attributes["http.query"] = _get_query(asgi_scope)
128+
129+
attributes["url.full"] = _get_url(
130+
asgi_scope, "http" if ty == "http" else "ws", headers.get("host")
131+
)
132+
133+
client = asgi_scope.get("client")
134+
if client and should_send_default_pii():
135+
attributes["client.address"] = {"REMOTE_ADDR": _get_ip(asgi_scope)}
136+
137+
return attributes

sentry_sdk/integrations/asgi.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sentry_sdk.consts import OP
1616
from sentry_sdk.integrations._asgi_common import (
1717
_get_headers,
18+
_get_request_attributes,
1819
_get_request_data,
1920
_get_url,
2021
)
@@ -23,7 +24,11 @@
2324
nullcontext,
2425
)
2526
from sentry_sdk.sessions import track_session
26-
from sentry_sdk.traces import StreamedSpan
27+
from sentry_sdk.traces import (
28+
StreamedSpan,
29+
SegmentSource,
30+
SOURCE_FOR_STYLE as SEGMENT_SOURCE_FOR_STYLE,
31+
)
2732
from sentry_sdk.tracing import (
2833
SOURCE_FOR_STYLE,
2934
Transaction,
@@ -40,6 +45,7 @@
4045
_get_installed_modules,
4146
reraise,
4247
capture_internal_exceptions,
48+
qualname_from_function,
4349
)
4450

4551
from typing import TYPE_CHECKING
@@ -235,7 +241,7 @@ async def _run_app(
235241
transaction_source, "value", transaction_source
236242
),
237243
"sentry.origin": self.span_origin,
238-
"asgi.type": ty,
244+
"network.protocol.name": ty,
239245
}
240246

241247
if ty in ("http", "websocket"):
@@ -301,6 +307,9 @@ async def _run_app(
301307
else nullcontext()
302308
)
303309

310+
for attribute, value in _get_request_attributes(scope):
311+
sentry_scope.set_attribute(attribute, value)
312+
304313
with span_ctx as span:
305314
try:
306315

@@ -329,13 +338,24 @@ async def _sentry_wrapped_send(
329338
return await send(event)
330339

331340
if asgi_version == 2:
332-
return await self.app(scope)(
341+
result = await self.app(scope)(
333342
receive, _sentry_wrapped_send
334343
)
335344
else:
336-
return await self.app(
345+
result = await self.app(
337346
scope, receive, _sentry_wrapped_send
338347
)
348+
349+
with capture_internal_exceptions():
350+
name, source = self._get_segment_name_and_source(
351+
self.transaction_style, scope
352+
)
353+
if isinstance(span, StreamedSpan):
354+
span.name = name
355+
span.set_attribute("sentry.span.source", source)
356+
357+
return result
358+
339359
except Exception as exc:
340360
suppress_chained_exceptions = (
341361
sentry_sdk.get_client()
@@ -424,3 +444,40 @@ def _get_transaction_name_and_source(
424444
return name, source
425445

426446
return name, source
447+
448+
def _get_segment_name_and_source(
449+
self: "SentryAsgiMiddleware", segment_style: str, asgi_scope: "Any"
450+
) -> "Tuple[str, str]":
451+
name = None
452+
source = SEGMENT_SOURCE_FOR_STYLE[segment_style]
453+
ty = asgi_scope.get("type")
454+
455+
if segment_style == "endpoint":
456+
endpoint = asgi_scope.get("endpoint")
457+
# Webframeworks like Starlette mutate the ASGI env once routing is
458+
# done, which is sometime after the request has started. If we have
459+
# an endpoint, overwrite our generic transaction name.
460+
if endpoint:
461+
name = qualname_from_function(endpoint) or ""
462+
else:
463+
name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None)
464+
source = SegmentSource.URL.value
465+
466+
elif segment_style == "url":
467+
# FastAPI includes the route object in the scope to let Sentry extract the
468+
# path from it for the transaction name
469+
route = asgi_scope.get("route")
470+
if route:
471+
path = getattr(route, "path", None)
472+
if path is not None:
473+
name = path
474+
else:
475+
name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None)
476+
source = SegmentSource.URL.value
477+
478+
if name is None:
479+
name = _DEFAULT_TRANSACTION_NAME
480+
source = SegmentSource.ROUTE.value
481+
return name, source
482+
483+
return name, source

0 commit comments

Comments
 (0)