Skip to content

feat(starlette): Support span streaming#6123

Open
ericapisani wants to merge 1 commit intomasterfrom
ep/py-2362-2mw
Open

feat(starlette): Support span streaming#6123
ericapisani wants to merge 1 commit intomasterfrom
ep/py-2362-2mw

Conversation

@ericapisani
Copy link
Copy Markdown
Member

Bring the Starlette integration to span-streaming parity with the FastAPI change that landed in #6118.

Under the trace_lifecycle: "stream" experiment the scope's transaction is None and the scope instead holds a StreamedSpan, so the existing middleware instrumentation (which called start_span(op=..., origin=...) + set_tag) and the profiler's current_scope.transaction.update_active_thread() hook in patch_request_response didn'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_middleware now uses a _start_middleware_span helper that calls sentry_sdk.traces.start_span with sentry.op, sentry.origin, and starlette.middleware_name attributes under streaming, and the existing sentry_sdk.start_span(op=..., origin=...) + set_tag otherwise.
  • The profiler hook detects StreamedSpan (excluding NoOpStreamedSpan) and calls _segment._update_active_thread(); otherwise it takes the legacy current_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, and test_span_origin. A new test_active_thread_id_span_streaming covers the segment's thread.id attribute under streaming. auto_enabling_integrations=False is set in streaming tests where auto-instrumented spans would otherwise leak into the captured span stream.

Refs PY-2362

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>
@linear-code
Copy link
Copy Markdown

linear-code Bot commented Apr 21, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Codecov Results 📊

13 passed | Total: 13 | Pass Rate: 100% | Execution Time: 8.75s

All tests are passing successfully.

❌ Patch coverage is 9.09%. Project has 14790 uncovered lines.

Files with missing lines (1)
File Patch % Lines
starlette.py 5.63% ⚠️ 352 Missing

Generated by Codecov Action

@ericapisani ericapisani marked this pull request as ready for review April 21, 2026 17:24
@ericapisani ericapisani requested a review from a team as a code owner April 21, 2026 17:24
Copy link
Copy Markdown
Contributor

@sentrivana sentrivana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, left a few comments -- we'll need to migrate the event processors as well.

attributes={
"sentry.op": op,
"sentry.origin": StarletteIntegration.origin,
"starlette.middleware_name": middleware_name,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

starlette.middleware_name is not in Sentry semantic conventions -- anything we set as an attribute on a streamed span needs to be there. So we either need to add it or not set it at all.

In this case, if I read the code right, it seems to be the same as the span name? In that case I'd omit the attribute. If we want to keep it, I'd propose a new attribute name in Sentry conventions that's not tied to Starlette specifically, so that we can use it across different frameworks and languages.

Comment on lines 499 to 501
sentry_scope.add_event_processor(
_make_request_event_processor(request, integration)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're adding an event processor here. In legacy mode, it would enrich any transaction event with additional data. Since streamed spans are not events, it'll not apply, so anytime there's an event processor in an integration, we need to come up with an equivalent way to apply the data to streaming segments as well.

The gist is basically:

  • Leave the event processor in place since it should continue working for other event types, like errors, as well, in the future.
  • The span streaming version of this will basically be us setting equivalent attributes (which need to be in conventions) on the segment span.
  • Event processors are normally run at the very end of the transaction lifecycle. Depending on what data they set, the span streaming equivalent might also need to be run towards the end of the segment's lifetime -- it depends on whether any of the data we set can be expected to be populated only later (like, for instance, the response status code).

Have a look at ASGI for prior art.

Ideally, this would've popped up in the tests. Looks like we're not really testing that the event processor applies the correct data, since the tests are passing even without it. That's not great. But we can also address that at a later point since it's out of scope of this PR.

Comment on lines 558 to 560
sentry_scope.add_event_processor(
_make_request_event_processor(request, integration)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll also need to port this event processor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants