Skip to content

Commit dddf367

Browse files
committed
fix(spans): Add http.route attribute to HTTP server spans
1 parent 1c3b50d commit dddf367

7 files changed

Lines changed: 71 additions & 4 deletions

File tree

sentry_sdk/consts.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,12 @@ class SPANDATA:
878878
Example: GET
879879
"""
880880

881+
HTTP_ROUTE = "http.route"
882+
"""
883+
The matched route, that is, the path template used to match the request.
884+
Example: /users/{id}
885+
"""
886+
881887
HTTP_QUERY = "http.query"
882888
"""
883889
The Query string present in the URL.

sentry_sdk/integrations/asgi.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,13 +246,19 @@ async def _run_app(
246246
span_ctx: "ContextManager[Union[Span, StreamedSpan, None]]"
247247
if span_streaming:
248248
segment: "Optional[StreamedSpan]" = None
249+
segment_source = getattr(
250+
transaction_source, "value", transaction_source
251+
)
249252
attributes: "Attributes" = {
250-
"sentry.segment.name.source": getattr(
251-
transaction_source, "value", transaction_source
252-
),
253+
"sentry.segment.name.source": segment_source,
253254
"sentry.origin": self.span_origin,
254255
"network.protocol.name": ty,
255256
}
257+
if (
258+
segment_source == SegmentNameSource.ROUTE.value
259+
and transaction_name != _DEFAULT_TRANSACTION_NAME
260+
):
261+
attributes[SPANDATA.HTTP_ROUTE] = transaction_name
256262

257263
if scope.get("client"):
258264
client_options = sentry_sdk.get_client().options
@@ -412,6 +418,13 @@ async def _sentry_wrapped_send(
412418
span.set_attribute(
413419
"sentry.segment.name.source", source
414420
)
421+
if (
422+
source == SegmentNameSource.ROUTE.value
423+
and name != _DEFAULT_TRANSACTION_NAME
424+
):
425+
span.set_attribute(
426+
SPANDATA.HTTP_ROUTE, name
427+
)
415428
finally:
416429
_asgi_middleware_applied.set(False)
417430

sentry_sdk/scope.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from sentry_sdk.traces import (
3232
_DEFAULT_PARENT_SPAN,
3333
NoOpStreamedSpan,
34+
SegmentNameSource,
3435
StreamedSpan,
3536
)
3637
from sentry_sdk.tracing import (
@@ -862,9 +863,12 @@ def set_transaction_name(self, name: str, source: "Optional[str]" = None) -> Non
862863
if isinstance(self._span, StreamedSpan):
863864
self._span._segment.name = name
864865
if source:
866+
source_value = getattr(source, "value", source)
865867
self._span._segment.set_attribute(
866-
"sentry.segment.name.source", getattr(source, "value", source)
868+
"sentry.segment.name.source", source_value
867869
)
870+
if source_value == SegmentNameSource.ROUTE.value:
871+
self._span._segment.set_attribute(SPANDATA.HTTP_ROUTE, name)
868872

869873
elif self._span.containing_transaction:
870874
self._span.containing_transaction.name = name

tests/integrations/asgi/test_asgi.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,7 @@ async def test_transaction_style(
670670

671671
assert span["name"] == expected_transaction
672672
assert span["attributes"]["sentry.segment.name.source"] == expected_source
673+
assert "http.route" not in span["attributes"]
673674

674675
else:
675676
(transaction_event,) = events
@@ -678,6 +679,40 @@ async def test_transaction_style(
678679
assert transaction_event["transaction_info"] == {"source": expected_source}
679680

680681

682+
@pytest.mark.asyncio
683+
async def test_http_route_set_for_route_segment_name(
684+
sentry_init,
685+
asgi3_app,
686+
capture_items,
687+
):
688+
sentry_init(
689+
traces_sample_rate=1.0,
690+
trace_lifecycle="stream",
691+
)
692+
app = SentryAsgiMiddleware(asgi3_app, transaction_style="url")
693+
694+
class Route:
695+
path = "/message/{message_id}"
696+
697+
scope = {
698+
"endpoint": asgi3_app,
699+
"route": Route(),
700+
"client": ("127.0.0.1", 60457),
701+
}
702+
703+
async with TestClient(app, scope=scope) as client:
704+
items = capture_items("span")
705+
await client.get("/message/123456")
706+
707+
sentry_sdk.flush()
708+
709+
assert len(items) == 1
710+
span = items[0].payload
711+
assert span["name"] == "/message/{message_id}"
712+
assert span["attributes"]["sentry.segment.name.source"] == "route"
713+
assert span["attributes"]["http.route"] == "/message/{message_id}"
714+
715+
681716
def mock_asgi2_app():
682717
pass
683718

tests/integrations/django/test_basic.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,10 @@ def test_transaction_style(
12761276

12771277
assert spans[2]["is_segment"] is True
12781278
assert spans[2]["attributes"]["sentry.segment.name.source"] == expected_source
1279+
if expected_source == "route":
1280+
assert spans[2]["attributes"]["http.route"] == expected_transaction
1281+
else:
1282+
assert "http.route" not in spans[2]["attributes"]
12791283

12801284
(event,) = (item.payload for item in items if item.type == "event")
12811285
else:

tests/integrations/fastapi/test_fastapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,7 @@ async def get_user(user_id: int):
770770
segment = segments[0]
771771
assert segment["name"] == "/api/users/{user_id}"
772772
assert segment["attributes"]["sentry.segment.name.source"] == "route"
773+
assert segment["attributes"]["http.route"] == "/api/users/{user_id}"
773774
else:
774775
(transaction_envelope,) = envelopes
775776
transaction_event = transaction_envelope.get_transaction_event()

tests/integrations/flask/test_flask.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ def test_transaction_or_segment_style(
135135
(segment,) = spans
136136
assert segment["name"] == expected_transaction
137137
assert segment["attributes"]["sentry.segment.name.source"] == expected_source
138+
if expected_source == "route":
139+
assert segment["attributes"]["http.route"] == expected_transaction
140+
else:
141+
assert "http.route" not in segment["attributes"]
138142
else:
139143
(_, event) = events
140144
assert event["transaction"] == expected_transaction

0 commit comments

Comments
 (0)