Skip to content

[do not merge] feat: Span streaming & new span API #1596

[do not merge] feat: Span streaming & new span API

[do not merge] feat: Span streaming & new span API #1596

Triggered via pull request March 11, 2026 09:59
@sentrivanasentrivana
synchronize #5551
Status Success
Total duration 19s
Artifacts

changelog-preview.yml

on: pull_request_target
changelog-preview  /  preview
14s
changelog-preview / preview
Fit to window
Zoom out
Zoom in

Annotations

9 errors and 13 warnings
Spans leak when Redis command throws exception: sentry_sdk/integrations/redis/_async_common.py#L145
When `old_execute_command` raises an exception, `db_span.__exit__()` and `cache_span.__exit__()` are never called because there's no `try/finally` block wrapping the await call. This differs from the sync version in `_sync_common.py` which correctly uses `try/finally` at lines 151-160. Leaking spans will cause incorrect timing data and potential memory/scope issues.
[8YY-F82] Spans leak when Redis command throws exception (additional location): sentry_sdk/integrations/redis/_sync_common.py#L158
When `old_execute_command` raises an exception, `db_span.__exit__()` and `cache_span.__exit__()` are never called because there's no `try/finally` block wrapping the await call. This differs from the sync version in `_sync_common.py` which correctly uses `try/finally` at lines 151-160. Leaking spans will cause incorrect timing data and potential memory/scope issues.
AttributeError when using legacy Span class - missing _to_traceparent, _to_baggage, _get_trace_context methods: sentry_sdk/scope.py#L587
The code calls `self.span._to_traceparent()`, `self.span._to_baggage()`, and `self._span._get_trace_context()` on the span object. However, the `Span` type from `tracing.py` (used in legacy non-streaming mode) only defines the non-underscore versions: `to_traceparent()`, `to_baggage()`, and `get_trace_context()`. This will cause `AttributeError` at runtime when using the SDK in non-streaming mode.
NoOpStreamedSpan._get_trace_context() and _dynamic_sampling_context() will raise AttributeError: sentry_sdk/traces.py#L586
Setting `self._segment = None` on line 586 without overriding `_dynamic_sampling_context()` and `_get_trace_context()` in `NoOpStreamedSpan` causes a runtime error. When `scope.get_trace_context()` is called with an active `NoOpStreamedSpan`, it invokes the inherited `_get_trace_context()` which calls `_dynamic_sampling_context()`, which accesses `self._segment._get_baggage()` - resulting in `AttributeError: 'NoneType' object has no attribute '_get_baggage'`.
UnboundLocalError when Redis command raises exception: sentry_sdk/integrations/redis/_sync_common.py#L158
In the `finally` block (lines 153-160), `_set_cache_data(cache_span, self, cache_properties, value)` references `value` which is only assigned if `old_execute_command` succeeds. If the Redis command raises an exception, `value` is never assigned, causing an `UnboundLocalError` in the finally block. This crashes the exception handling and masks the original Redis exception, breaking error propagation to callers.
[384-RKF] UnboundLocalError when Redis command raises exception (additional location): sentry_sdk/integrations/redis/_async_common.py#L126
In the `finally` block (lines 153-160), `_set_cache_data(cache_span, self, cache_properties, value)` references `value` which is only assigned if `old_execute_command` succeeds. If the Redis command raises an exception, `value` is never assigned, causing an `UnboundLocalError` in the finally block. This crashes the exception handling and masks the original Redis exception, breaking error propagation to callers.
Calling _get_trace_context() on NoOpStreamedSpan will crash with AttributeError: sentry_sdk/scope.py#L611
When span streaming mode is enabled and a span is sampled out, a `NoOpStreamedSpan` is created and set as the active span on the scope. The `NoOpStreamedSpan` class sets `self._segment = None` but does not override `_get_trace_context()` or `_dynamic_sampling_context()`. When `scope.get_trace_context()` is called (line 611), it invokes `self._span._get_trace_context()`. For `NoOpStreamedSpan`, this inherits from `StreamedSpan` which calls `self._dynamic_sampling_context()`, which in turn calls `self._segment._get_baggage()`. Since `_segment` is `None`, this raises `AttributeError: 'NoneType' object has no attribute '_get_baggage'`.
[KZP-Z94] Calling _get_trace_context() on NoOpStreamedSpan will crash with AttributeError (additional location): sentry_sdk/traces.py#L586
When span streaming mode is enabled and a span is sampled out, a `NoOpStreamedSpan` is created and set as the active span on the scope. The `NoOpStreamedSpan` class sets `self._segment = None` but does not override `_get_trace_context()` or `_dynamic_sampling_context()`. When `scope.get_trace_context()` is called (line 611), it invokes `self._span._get_trace_context()`. For `NoOpStreamedSpan`, this inherits from `StreamedSpan` which calls `self._dynamic_sampling_context()`, which in turn calls `self._segment._get_baggage()`. Since `_segment` is `None`, this raises `AttributeError: 'NoneType' object has no attribute '_get_baggage'`.
[KZP-Z94] Calling _get_trace_context() on NoOpStreamedSpan will crash with AttributeError (additional location): sentry_sdk/integrations/strawberry.py#L226
When span streaming mode is enabled and a span is sampled out, a `NoOpStreamedSpan` is created and set as the active span on the scope. The `NoOpStreamedSpan` class sets `self._segment = None` but does not override `_get_trace_context()` or `_dynamic_sampling_context()`. When `scope.get_trace_context()` is called (line 611), it invokes `self._span._get_trace_context()`. For `NoOpStreamedSpan`, this inherits from `StreamedSpan` which calls `self._dynamic_sampling_context()`, which in turn calls `self._segment._get_baggage()`. Since `_segment` is `None`, this raises `AttributeError: 'NoneType' object has no attribute '_get_baggage'`.
changelog-preview / preview
Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
Span streaming mode ignores http_methods_to_capture filter for HTTP requests: sentry_sdk/integrations/asgi.py#L238
In the span streaming branch (lines 218-241), a span is always created via `start_span()` at line 238, regardless of whether the HTTP method is in `http_methods_to_capture`. In contrast, the legacy branch (lines 243-275) only creates a transaction when `method in self.http_methods_to_capture`. This causes HTTP requests with methods like OPTIONS, HEAD, etc. (if not in the filter) to create unnecessary spans in streaming mode, potentially impacting performance and creating unwanted telemetry data.
HTTP status code attribute missing for StreamedSpan in sync client: sentry_sdk/integrations/httpx.py#L117
When using span streaming mode, the HTTP response status code is not recorded as an attribute via `SPANDATA.HTTP_STATUS_CODE`. Only `span.status` is set to "ok" or "error". This breaks consistency with the legacy API where `set_http_status()` sets both the status and the numeric status code. Features that depend on the status code attribute (like `trace_ignore_status_codes` filtering or breadcrumb creation) will not work correctly for streamed spans.
[HXN-2TP] HTTP status code attribute missing for StreamedSpan in sync client (additional location): sentry_sdk/integrations/httpx.py#L200
When using span streaming mode, the HTTP response status code is not recorded as an attribute via `SPANDATA.HTTP_STATUS_CODE`. Only `span.status` is set to "ok" or "error". This breaks consistency with the legacy API where `set_http_status()` sets both the status and the numeric status code. Features that depend on the status code attribute (like `trace_ignore_status_codes` filtering or breadcrumb creation) will not work correctly for streamed spans.
StreamedSpan error cleanup missing - spans never closed when errors occur in streaming mode: sentry_sdk/integrations/anthropic.py#L572
The change from `span is not None` to `isinstance(span, Span)` excludes `StreamedSpan` from the error cleanup logic. When an exception occurs in the Anthropic API call while using span streaming mode, the `StreamedSpan` will have its status set to `SpanStatus.ERROR` by `set_span_errored()`, but the span's `__exit__` method is never called. This results in: (1) the span remaining as the current span on the scope (scope pollution), (2) the span never being sent to Sentry since `_end()` is never called, and (3) potential memory leaks.
[VTM-7BA] StreamedSpan error cleanup missing - spans never closed when errors occur in streaming mode (additional location): sentry_sdk/integrations/anthropic.py#L610
The change from `span is not None` to `isinstance(span, Span)` excludes `StreamedSpan` from the error cleanup logic. When an exception occurs in the Anthropic API call while using span streaming mode, the `StreamedSpan` will have its status set to `SpanStatus.ERROR` by `set_span_errored()`, but the span's `__exit__` method is never called. This results in: (1) the span remaining as the current span on the scope (scope pollution), (2) the span never being sent to Sentry since `_end()` is never called, and (3) potential memory leaks.
Span streaming mode ignores http_methods_to_capture filter: sentry_sdk/integrations/asgi.py#L222
In span streaming mode, when `ty` is 'http' but the method is NOT in `http_methods_to_capture`, the code falls through to always create a span (lines 238-240). In non-streaming mode, `transaction` remains `None` and no span is created. This causes unwanted tracing of HTTP methods that should be ignored (e.g., OPTIONS, HEAD), creating noise and potentially impacting performance.
Span error status not set when exception occurs in GraphQL operations with span streaming: sentry_sdk/integrations/graphene.py#L168
The `graphql_span` context manager manually calls `_graphql_span.end()` in a `finally` block instead of using the span as a context manager (with `with _graphql_span:`). When an exception occurs during the `yield` (i.e., during GraphQL execution), the `StreamedSpan.__exit__` method is not invoked, which means the span's status is never set to `SpanStatus.ERROR`. This causes telemetry to incorrectly report successful spans even when the operation failed with an exception.
Missing HTTP status code attribute in StreamedSpan for sync httpx client: sentry_sdk/integrations/httpx.py#L116
When span streaming mode is enabled, the HTTP status code attribute (`http.response.status_code`) is never set on the span. The code sets `span.status` and `span.set_attribute("reason", ...)` but omits `span.set_attribute(SPANDATA.HTTP_STATUS_CODE, rv.status_code)`. This contrasts with the legacy `Span` path which calls `set_http_status()` that sets this attribute. This results in incomplete HTTP span data in streaming mode.
[BZ3-QGD] Missing HTTP status code attribute in StreamedSpan for sync httpx client (additional location): sentry_sdk/integrations/httpx.py#L199
When span streaming mode is enabled, the HTTP status code attribute (`http.response.status_code`) is never set on the span. The code sets `span.status` and `span.set_attribute("reason", ...)` but omits `span.set_attribute(SPANDATA.HTTP_STATUS_CODE, rv.status_code)`. This contrasts with the legacy `Span` path which calls `set_http_status()` that sets this attribute. This results in incomplete HTTP span data in streaming mode.
[BZ3-QGD] Missing HTTP status code attribute in StreamedSpan for sync httpx client (additional location): sentry_sdk/integrations/stdlib.py#L175
When span streaming mode is enabled, the HTTP status code attribute (`http.response.status_code`) is never set on the span. The code sets `span.status` and `span.set_attribute("reason", ...)` but omits `span.set_attribute(SPANDATA.HTTP_STATUS_CODE, rv.status_code)`. This contrasts with the legacy `Span` path which calls `set_http_status()` that sets this attribute. This results in incomplete HTTP span data in streaming mode.
set_transaction_name skips updating transaction_info for NoOpStreamedSpan: sentry_sdk/scope.py#L831
In `set_transaction_name`, when `self._span` is a `NoOpStreamedSpan`, the method returns early at line 831, which prevents `self._transaction_info["source"]` from being updated (lines 845-846). This is scope-level data that should still be set regardless of the span type. Events captured while this scope is active will be missing the transaction source in their `transaction_info`, causing inconsistent metadata in Sentry.
Dict rule with unrecognized keys in ignore_spans matches all spans: sentry_sdk/tracing_utils.py#L1601
In `is_ignored_span()`, when a rule dict contains keys other than 'name' or 'attributes' (e.g., `{"foo": "bar"}`), both `name_matches` and `attributes_match` default to `True` and are never updated. This causes such rules to match every span, which is almost certainly not the user's intent. While TypedDict provides static type checking, Python doesn't enforce these constraints at runtime.