diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 82a9f27ec8..c9323623d0 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -878,6 +878,12 @@ class SPANDATA: Example: GET """ + HTTP_ROUTE = "http.route" + """ + The matched route, that is, the path template used to match the request. + Example: /users/{id} + """ + HTTP_QUERY = "http.query" """ The Query string present in the URL. diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index d594c3504d..17ffbaacf7 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -281,6 +281,7 @@ async def _run_app( attributes=attributes, parent_span=None, ) + sentry_scope.get_current_scope()._server_segment_span = segment else: sentry_sdk.traces.new_trace() @@ -292,6 +293,9 @@ async def _run_app( attributes=attributes, parent_span=None, ) + sentry_scope.get_current_scope()._server_segment_span = ( + segment + ) span_ctx = segment or nullcontext() diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py index dc408797e3..6f43f515b3 100644 --- a/sentry_sdk/integrations/fastapi.py +++ b/sentry_sdk/integrations/fastapi.py @@ -79,6 +79,10 @@ def _set_transaction_name_and_source( if path is not None: name = path + server_span = sentry_sdk.get_current_scope()._server_segment_span + if server_span is not None: + server_span.set_attribute(SPANDATA.HTTP_ROUTE, name) + if not name: name = _DEFAULT_TRANSACTION_NAME source = TransactionSource.ROUTE diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 9587ed84fd..4d547a3f48 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -230,6 +230,7 @@ class Scope: "_error_processors", "_should_capture", "_span", + "_server_segment_span", "_session", "_attachments", "_force_auto_session_tracking", @@ -257,6 +258,8 @@ def __init__( self._n_breadcrumbs_truncated: int = 0 self._gen_ai_original_message_count: "Dict[str, int]" = {} + self._server_segment_span: "Optional[StreamedSpan]" = None + self.client: "sentry_sdk.client.BaseClient" = NonRecordingClient() if client is not None: @@ -296,6 +299,7 @@ def __copy__(self) -> "Scope": rv._should_capture = self._should_capture rv._span = self._span + rv._server_segment_span = self._server_segment_span rv._session = self._session rv._force_auto_session_tracking = self._force_auto_session_tracking rv._attachments = self._attachments.copy() diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index 4a1df70d7c..8e5d7f5ca9 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -883,6 +883,7 @@ async def get_user(user_id: int): segment = segments[0] assert segment["name"] == "/api/users/{user_id}" assert segment["attributes"]["sentry.segment.name.source"] == "route" + assert segment["attributes"]["http.route"] == "/api/users/{user_id}" else: (transaction_envelope,) = envelopes transaction_event = transaction_envelope.get_transaction_event()