1515from sentry_sdk .consts import OP
1616from sentry_sdk .integrations ._asgi_common import (
1717 _get_headers ,
18+ _get_request_attributes ,
1819 _get_request_data ,
1920 _get_url ,
2021)
2324 nullcontext ,
2425)
2526from 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+ )
2732from sentry_sdk .tracing import (
2833 SOURCE_FOR_STYLE ,
2934 Transaction ,
4045 _get_installed_modules ,
4146 reraise ,
4247 capture_internal_exceptions ,
48+ qualname_from_function ,
4349)
4450
4551from 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" ):
@@ -302,6 +308,12 @@ async def _run_app(
302308 )
303309
304310 with span_ctx as span :
311+ if isinstance (span , StreamedSpan ):
312+ for attribute , value in _get_request_attributes (
313+ scope
314+ ).items ():
315+ span .set_attribute (attribute , value )
316+
305317 try :
306318
307319 async def _sentry_wrapped_send (
@@ -336,6 +348,7 @@ async def _sentry_wrapped_send(
336348 return await self .app (
337349 scope , receive , _sentry_wrapped_send
338350 )
351+
339352 except Exception as exc :
340353 suppress_chained_exceptions = (
341354 sentry_sdk .get_client ()
@@ -350,6 +363,28 @@ async def _sentry_wrapped_send(
350363 with capture_internal_exceptions ():
351364 self ._capture_request_exception (exc )
352365 reraise (* exc_info )
366+
367+ finally :
368+ if isinstance (span , StreamedSpan ):
369+ already_set = (
370+ span is not None
371+ and span .name != _DEFAULT_TRANSACTION_NAME
372+ and span .get_attributes ().get ("sentry.span.source" )
373+ in [
374+ SegmentSource .COMPONENT .value ,
375+ SegmentSource .ROUTE .value ,
376+ SegmentSource .CUSTOM .value ,
377+ ]
378+ )
379+ with capture_internal_exceptions ():
380+ if not already_set :
381+ name , source = (
382+ self ._get_segment_name_and_source (
383+ self .transaction_style , scope
384+ )
385+ )
386+ span .name = name
387+ span .set_attribute ("sentry.span.source" , source )
353388 finally :
354389 _asgi_middleware_applied .set (False )
355390
@@ -424,3 +459,40 @@ def _get_transaction_name_and_source(
424459 return name , source
425460
426461 return name , source
462+
463+ def _get_segment_name_and_source (
464+ self : "SentryAsgiMiddleware" , segment_style : str , asgi_scope : "Any"
465+ ) -> "Tuple[str, str]" :
466+ name = None
467+ source = SEGMENT_SOURCE_FOR_STYLE [segment_style ].value
468+ ty = asgi_scope .get ("type" )
469+
470+ if segment_style == "endpoint" :
471+ endpoint = asgi_scope .get ("endpoint" )
472+ # Webframeworks like Starlette mutate the ASGI env once routing is
473+ # done, which is sometime after the request has started. If we have
474+ # an endpoint, overwrite our generic transaction name.
475+ if endpoint :
476+ name = qualname_from_function (endpoint ) or ""
477+ else :
478+ name = _get_url (asgi_scope , "http" if ty == "http" else "ws" , host = None )
479+ source = SegmentSource .URL .value
480+
481+ elif segment_style == "url" :
482+ # FastAPI includes the route object in the scope to let Sentry extract the
483+ # path from it for the transaction name
484+ route = asgi_scope .get ("route" )
485+ if route :
486+ path = getattr (route , "path" , None )
487+ if path is not None :
488+ name = path
489+ else :
490+ name = _get_url (asgi_scope , "http" if ty == "http" else "ws" , host = None )
491+ source = SegmentSource .URL .value
492+
493+ if name is None :
494+ name = _DEFAULT_TRANSACTION_NAME
495+ source = SegmentSource .ROUTE .value
496+ return name , source
497+
498+ return name , source
0 commit comments