Skip to content

Commit e043a2f

Browse files
committed
fix(starlette): Set transaction name on current scope in sync handler
The sync request/response handler passed the _isolation_ scope to `_set_transaction_name_and_source`, but the transaction/segment span lives on the _current_ scope. As a result the route-resolved name never reached the span for sync endpoints, which were instead named by the raw URL from the ASGI middleware (`transaction_info.source` of `url` rather than `route`). Async handlers already used the current scope and were unaffected. Pass the current scope (already computed above) so sync and async handlers behave identically: - streaming: the segment name / `sentry.segment.name.source` are route-based - static: the transaction event name / source are route-based For parametrized routes this also removes high-cardinality URL transaction names for sync endpoints.
1 parent 1c3b50d commit e043a2f

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

sentry_sdk/integrations/starlette.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any":
632632
request = args[0]
633633

634634
_set_transaction_name_and_source(
635-
sentry_scope, integration.transaction_style, request
635+
current_scope, integration.transaction_style, request
636636
)
637637

638638
extractor = StarletteRequestExtractor(request)

tests/integrations/starlette/test_starlette.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,50 @@ def test_active_thread_id_span_streaming(sentry_init, capture_items, endpoint):
14991499
assert str(data["active"]) == segments[0]["attributes"]["thread.id"]
15001500

15011501

1502+
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
1503+
def test_transaction_name_span_streaming(sentry_init, capture_items, endpoint):
1504+
# Sync handlers resolve the route on the isolation scope, which does not
1505+
# hold the streamed segment, so the route-based name must still reach the
1506+
# segment (rather than the raw-URL name set by the ASGI middleware).
1507+
sentry_init(
1508+
auto_enabling_integrations=False,
1509+
integrations=[StarletteIntegration(transaction_style="url")],
1510+
traces_sample_rate=1.0,
1511+
trace_lifecycle="stream",
1512+
)
1513+
app = starlette_app_factory()
1514+
1515+
items = capture_items("span")
1516+
1517+
client = TestClient(app)
1518+
response = client.get(endpoint)
1519+
assert response.status_code == 200
1520+
1521+
sentry_sdk.flush()
1522+
1523+
segments = [item.payload for item in items if item.payload.get("is_segment")]
1524+
assert len(segments) == 1
1525+
assert segments[0]["name"] == endpoint
1526+
assert segments[0]["attributes"]["sentry.segment.name.source"] == "route"
1527+
1528+
1529+
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
1530+
def test_transaction_name_static(sentry_init, capture_events, endpoint):
1531+
sentry_init(
1532+
integrations=[StarletteIntegration(transaction_style="url")],
1533+
traces_sample_rate=1.0,
1534+
)
1535+
events = capture_events()
1536+
1537+
client = TestClient(starlette_app_factory())
1538+
response = client.get(endpoint)
1539+
assert response.status_code == 200
1540+
1541+
(transaction,) = [e for e in events if e.get("type") == "transaction"]
1542+
assert transaction["transaction"] == endpoint
1543+
assert transaction["transaction_info"] == {"source": "route"}
1544+
1545+
15021546
def test_original_request_not_scrubbed(sentry_init, capture_events):
15031547
sentry_init(integrations=[StarletteIntegration()])
15041548

0 commit comments

Comments
 (0)