[do not merge] feat: Span streaming & new span API #5551
6 issues
code-review: Found 6 issues (3 high, 2 medium, 1 low)
High
Spans leak when Redis command throws exception - `sentry_sdk/integrations/redis/_async_common.py:145`
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.
Also found at:
sentry_sdk/integrations/redis/_sync_common.py:158
AttributeError when using legacy Span class - missing _to_traceparent, _to_baggage, _get_trace_context methods - `sentry_sdk/scope.py:587-611`
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:586`
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'.
Medium
Span streaming mode ignores http_methods_to_capture filter for HTTP requests - `sentry_sdk/integrations/asgi.py:238-241`
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:117-118`
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.
Also found at:
sentry_sdk/integrations/httpx.py:200-201
Low
isinstance check does not distinguish StreamedSpan from NoOpStreamedSpan - `tests/tracing/test_span_streaming.py:1008`
The isinstance(span, StreamedSpan) assertion on line 1008 will always pass even when span is a NoOpStreamedSpan, because NoOpStreamedSpan inherits from StreamedSpan. This test is meant to verify that non-ignored spans are real StreamedSpan instances (not NoOp), but the current check provides a false sense of security since it cannot distinguish between the two types. The test relies solely on the span.sampled is True check which precedes it, making the isinstance assertion redundant and misleading.
Duration: 20m 12s · Tokens: 15.0M in / 172.9k out · Cost: $20.23 (+extraction: $0.01, +merge: $0.00, +fix_gate: $0.01)
Annotations
Check failure on line 145 in sentry_sdk/integrations/redis/_async_common.py
github-actions / warden: code-review
Spans leak when Redis command throws exception
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.
Check failure on line 158 in sentry_sdk/integrations/redis/_sync_common.py
github-actions / warden: code-review
[8YY-F82] Spans leak when Redis command throws exception (additional location)
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.
Check failure on line 611 in sentry_sdk/scope.py
github-actions / warden: code-review
AttributeError when using legacy Span class - missing _to_traceparent, _to_baggage, _get_trace_context methods
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.
Check failure on line 586 in sentry_sdk/traces.py
github-actions / warden: code-review
NoOpStreamedSpan._get_trace_context() and _dynamic_sampling_context() will raise AttributeError
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'`.
Check warning on line 241 in sentry_sdk/integrations/asgi.py
github-actions / warden: code-review
Span streaming mode ignores http_methods_to_capture filter for HTTP requests
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.
Check warning on line 118 in sentry_sdk/integrations/httpx.py
github-actions / warden: code-review
HTTP status code attribute missing for StreamedSpan in sync client
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.
Check warning on line 201 in sentry_sdk/integrations/httpx.py
github-actions / warden: code-review
[HXN-2TP] HTTP status code attribute missing for StreamedSpan in sync client (additional location)
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.