Conversation
Add span-streaming support to the Starlette integration so middleware spans and active-thread tracking work under the `trace_lifecycle: "stream"` experiment, while preserving the legacy transaction-based behavior. When span streaming is enabled, `_enable_span_for_middleware` starts middleware spans via `sentry_sdk.traces.start_span` with `sentry.op`, `sentry.origin`, and `starlette.middleware_name` attributes instead of the legacy `start_span(op=..., origin=...)` + tag pattern. In `patch_request_response`, when the current scope holds a `StreamedSpan` (and not a `NoOpStreamedSpan`), the profiler hook now calls `_segment._update_active_thread()`; otherwise the legacy `current_scope.transaction.update_active_thread()` path is preserved. Tests are parametrized across streaming and static modes for `test_middleware_spans`, `test_middleware_spans_disabled`, `test_middleware_callback_spans`, and `test_span_origin`. A new `test_active_thread_id_span_streaming` verifies the segment's `thread.id` attribute under streaming. `auto_enabling_integrations` is disabled in tests where auto-instrumented spans would leak into the captured span stream. Refs PY-2362 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Results 📊✅ 13 passed | Total: 13 | Pass Rate: 100% | Execution Time: 8.91s All tests are passing successfully. ❌ Patch coverage is 8.11%. Project has 14810 uncovered lines. Files with missing lines (1)
Generated by Codecov Action |
sentrivana
left a comment
There was a problem hiding this comment.
Nice, left a few comments -- we'll need to migrate the event processors as well.
When span streaming is enabled, attach the request body (serialized via a JSON default that unwraps AnnotatedValue) to the active segment span as `http.request.body.data` inside `patch_request_response`. This mirrors the body capture that the legacy request-event processor does, so streamed spans carry the same payload context without relying on transaction events. PII scrubbing is intentionally not applied here — under span streaming, sanitization is expected to happen in `before_send_span` hooks. Tests cover the unscrubbed passthrough, a top-level AnnotatedValue (raw bytes), and a nested AnnotatedValue inside a multipart form upload. Refs PY-2362 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ontent arg which only happens once httpx is part of starlette
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 50352ea. Configure here.
Under span streaming with middleware_spans=True, sentry_sdk.get_current_span() returns the innermost middleware span rather than the segment, so http.request.body.data was being attached to a middleware child span. Route the attribute through current_span._segment so it always lands on the segment, mirroring the _update_active_thread pattern in _sentry_sync_func. Also deduplicate the sentry_sdk.get_client() call in _sentry_async_func, and parametrize the three body-data streaming tests over middleware_spans=[False, True]. Refs PY-2362 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename the `starlette.middleware_name` span attribute to `middleware.name` on middleware spans emitted under span streaming. The non-streaming tag remains unchanged. Update the affected test assertions accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| attributes={ | ||
| "sentry.op": op, | ||
| "sentry.origin": StarletteIntegration.origin, | ||
| "middleware.name": middleware_name, |
There was a problem hiding this comment.
The associated convention PR changed this from starlette.middleware.name to middleware.name. I left the legacy stream alone because I didn't want to break existing functionality for users.

Bring the Starlette integration to span-streaming parity with the FastAPI change that landed in #6118.
Under the
trace_lifecycle: "stream"experiment the scope'stransactionisNoneand the scope instead holds aStreamedSpan, so the existing middleware instrumentation (which calledstart_span(op=..., origin=...) + set_tag) and the profiler'scurrent_scope.transaction.update_active_thread()hook inpatch_request_responsedidn't function under streaming. This PR routes both through the streaming APIs when streaming is enabled while leaving the legacy transaction-based path untouched:_enable_span_for_middlewarenow uses a_start_middleware_spanhelper that callssentry_sdk.traces.start_spanwithsentry.op,sentry.origin, andstarlette.middleware_nameattributes under streaming, and the existingsentry_sdk.start_span(op=..., origin=...) + set_tagotherwise.StreamedSpan(excludingNoOpStreamedSpan) and calls_segment._update_active_thread(); otherwise it takes the legacycurrent_scope.transaction.update_active_thread()branch.Tests are parametrized across streaming and static modes for
test_middleware_spans,test_middleware_spans_disabled,test_middleware_callback_spans, andtest_span_origin. A newtest_active_thread_id_span_streamingcovers the segment'sthread.idattribute under streaming.auto_enabling_integrations=Falseis set in streaming tests where auto-instrumented spans would otherwise leak into the captured span stream.Refs PY-2362