Skip to content

Commit 5c6a9eb

Browse files
feat(starlette): Add http.route attribute (#7338)
Set the `http.route` attribute on the ASGI server span in patches for Starlette endpoints. Co-authored-by: Matt Quinn <matt.quinn@sentry.io>
1 parent 5c83554 commit 5c6a9eb

2 files changed

Lines changed: 59 additions & 15 deletions

File tree

‎sentry_sdk/integrations/starlette.py‎

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,20 @@ async def _create_span_call(
184184
if integration is None:
185185
return await old_call(app, scope, receive, send, **kwargs)
186186

187+
route_path, name_source = _http_route_and_source_from_router(scope)
188+
189+
server_span = sentry_sdk.get_current_scope()._server_segment_span
190+
if (
191+
server_span is not None
192+
and route_path is not None
193+
and name_source == TransactionSource.ROUTE
194+
):
195+
server_span.set_attribute(SPANDATA.HTTP_ROUTE, route_path)
196+
187197
# Update transaction name with middleware name
188-
name, source = _get_transaction_from_middleware(app, scope, integration)
198+
name, source = _get_transaction_from_middleware(
199+
app, integration, route_path=route_path, name_source=name_source
200+
)
189201

190202
if name is not None:
191203
sentry_sdk.get_current_scope().set_transaction_name(
@@ -541,10 +553,22 @@ async def _wrap_async_handler(
541553

542554
request = args[0]
543555

556+
route_path, name_source = _http_route_and_source_from_router(request.scope)
557+
558+
server_span = sentry_sdk.get_current_scope()._server_segment_span
559+
if (
560+
server_span is not None
561+
and route_path is not None
562+
and name_source == TransactionSource.ROUTE
563+
):
564+
server_span.set_attribute(SPANDATA.HTTP_ROUTE, route_path)
565+
544566
_set_transaction_name_and_source(
545567
sentry_sdk.get_current_scope(),
546568
integration.transaction_style,
547-
request,
569+
endpoint=request.scope.get("endpoint"),
570+
route_path=route_path,
571+
name_source=name_source,
548572
)
549573

550574
sentry_scope = sentry_sdk.get_isolation_scope()
@@ -647,8 +671,24 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any":
647671

648672
request = args[0]
649673

674+
route_path, name_source = _http_route_and_source_from_router(
675+
request.scope
676+
)
677+
678+
server_span = sentry_sdk.get_current_scope()._server_segment_span
679+
if (
680+
server_span is not None
681+
and route_path is not None
682+
and name_source == TransactionSource.ROUTE
683+
):
684+
server_span.set_attribute(SPANDATA.HTTP_ROUTE, route_path)
685+
650686
_set_transaction_name_and_source(
651-
current_scope, integration.transaction_style, request
687+
current_scope,
688+
integration.transaction_style,
689+
endpoint=request.scope.get("endpoint"),
690+
route_path=route_path,
691+
name_source=name_source,
652692
)
653693

654694
extractor = StarletteRequestExtractor(request)
@@ -843,7 +883,7 @@ async def json(self: "StarletteRequestExtractor") -> "Optional[Dict[str, Any]]":
843883
return None
844884

845885

846-
def _transaction_name_and_source_from_router(
886+
def _http_route_and_source_from_router(
847887
scope: "StarletteScope",
848888
) -> "Tuple[Optional[str], TransactionSource]":
849889
router = scope.get("router")
@@ -864,18 +904,20 @@ def _transaction_name_and_source_from_router(
864904

865905

866906
def _set_transaction_name_and_source(
867-
scope: "sentry_sdk.Scope", transaction_style: str, request: "Any"
907+
scope: "sentry_sdk.Scope",
908+
transaction_style: str,
909+
endpoint: "Optional[Callable[..., Any]]",
910+
route_path: "Optional[str]",
911+
name_source: "TransactionSource",
868912
) -> None:
869913
name = None
870914
source = SOURCE_FOR_STYLE[transaction_style]
871915

872-
if transaction_style == "endpoint":
873-
endpoint = request.scope.get("endpoint")
874-
if endpoint:
875-
name = transaction_from_function(endpoint) or None
916+
if transaction_style == "endpoint" and endpoint:
917+
name = transaction_from_function(endpoint) or None
876918

877919
elif transaction_style == "url":
878-
name, source = _transaction_name_and_source_from_router(request.scope)
920+
name, source = route_path, name_source
879921

880922
if name is None:
881923
name = _DEFAULT_TRANSACTION_NAME
@@ -885,7 +927,10 @@ def _set_transaction_name_and_source(
885927

886928

887929
def _get_transaction_from_middleware(
888-
app: "Any", asgi_scope: "Dict[str, Any]", integration: "StarletteIntegration"
930+
app: "Any",
931+
integration: "StarletteIntegration",
932+
route_path: "Optional[str]",
933+
name_source: "TransactionSource",
889934
) -> "Tuple[Optional[str], Optional[str]]":
890935
name = None
891936
source = None
@@ -894,6 +939,6 @@ def _get_transaction_from_middleware(
894939
name = transaction_from_function(app.__class__)
895940
source = TransactionSource.COMPONENT
896941
elif integration.transaction_style == "url":
897-
name, source = _transaction_name_and_source_from_router(asgi_scope)
942+
name, source = route_path, name_source
898943

899944
return name, source

‎tests/integrations/starlette/test_starlette.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,9 +1638,7 @@ def test_active_thread_id_span_streaming(sentry_init, capture_items, endpoint):
16381638

16391639

16401640
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
1641-
def test_segment_name_is_route_resolved_name_span_streaming(
1642-
sentry_init, capture_items, endpoint
1643-
):
1641+
def test_http_route_span_streaming(sentry_init, capture_items, endpoint):
16441642
sentry_init(
16451643
auto_enabling_integrations=False,
16461644
integrations=[StarletteIntegration(transaction_style="url")],
@@ -1661,6 +1659,7 @@ def test_segment_name_is_route_resolved_name_span_streaming(
16611659
assert len(segments) == 1
16621660
assert segments[0]["name"] == endpoint
16631661
assert segments[0]["attributes"]["sentry.segment.name.source"] == "route"
1662+
assert segments[0]["attributes"]["http.route"] == endpoint
16641663

16651664

16661665
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])

0 commit comments

Comments
 (0)