[do not merge] feat: Span streaming & new span API #16282
Annotations
5 errors and 13 warnings
|
StreamedSpan not imported at runtime causing NameError:
sentry_sdk/integrations/graphene.py#L167
The `StreamedSpan` class is only imported within the `TYPE_CHECKING` block (line 30), which means it's not available at runtime. When span streaming is enabled and the code reaches line 167 (`if isinstance(_graphql_span, StreamedSpan):`), it will raise `NameError: name 'StreamedSpan' is not defined`. Other integrations like `celery/__init__.py` and `strawberry.py` correctly import `StreamedSpan` at the top level, not just for type checking.
|
|
[HCZ-ZPE] StreamedSpan not imported at runtime causing NameError (additional location):
sentry_sdk/integrations/redis/utils.py#L152
The `StreamedSpan` class is only imported within the `TYPE_CHECKING` block (line 30), which means it's not available at runtime. When span streaming is enabled and the code reaches line 167 (`if isinstance(_graphql_span, StreamedSpan):`), it will raise `NameError: name 'StreamedSpan' is not defined`. Other integrations like `celery/__init__.py` and `strawberry.py` correctly import `StreamedSpan` at the top level, not just for type checking.
|
|
StreamedSpan created but not started - spans will silently fail to send:
sentry_sdk/integrations/rust_tracing.py#L216
When `sentry_sdk.traces.start_span()` is called (lines 216, 231), the returned `StreamedSpan` is assigned to `scope.span` but never started via `start()` or context manager entry. When `on_close()` later calls `finish()` (which calls `end()` -> `__exit__()`), the span will fail to end properly because `_context_manager_state` was never set. The error is silently caught by `capture_internal_exceptions()`, so spans created in streaming mode will never be sent to Sentry.
|
|
StreamedSpan is only imported for type checking but used at runtime:
sentry_sdk/integrations/graphene.py#L167
`StreamedSpan` is imported under `TYPE_CHECKING` (line 30) but is used in an `isinstance()` check at runtime (line 167). When span streaming mode is enabled, executing a GraphQL query will raise `NameError: name 'StreamedSpan' is not defined` when the code reaches the `finally` block.
|
|
[UBH-6BM] StreamedSpan is only imported for type checking but used at runtime (additional location):
sentry_sdk/integrations/anthropic.py#L572
`StreamedSpan` is imported under `TYPE_CHECKING` (line 30) but is used in an `isinstance()` check at runtime (line 167). When span streaming mode is enabled, executing a GraphQL query will raise `NameError: name 'StreamedSpan' is not defined` when the code reaches the `finally` block.
|
|
Redundant span serialization causes O(2n) serialization overhead:
sentry_sdk/_span_batcher.py#L78
Each span is serialized via `_to_transport_format()` twice: once in `_estimate_size()` during `add()` for size tracking, and again in `_flush()` when building the envelope. For high-throughput scenarios with many spans, this doubles the serialization cost. Consider caching the serialized form or computing size from pre-cached data.
|
|
StreamedSpan status always set to ERROR regardless of status parameter:
sentry_sdk/integrations/celery/__init__.py#L104
The `_set_status` function receives a `status` parameter (e.g., "aborted" or "internal_error"), but when the span is a `StreamedSpan`, it always sets `SpanStatus.ERROR` instead of using the passed `status` value. Since `StreamedSpan.set_status()` accepts `Union[SpanStatus, str]`, it can handle string values directly. This causes control flow exceptions (which should have "aborted" status) to be incorrectly marked as "error", losing the semantic distinction between expected control flow aborts and actual errors.
|
|
Spans not closed on exception in async Redis execute_command:
sentry_sdk/integrations/redis/_async_common.py#L135
In `_sentry_execute_command`, both `db_span` and `cache_span` are entered manually via `__enter__()` but if `await old_execute_command(self, name, *args, **kwargs)` raises an exception, the corresponding `__exit__()` calls are never executed. This causes the spans to remain open indefinitely, leading to missing telemetry data and potential memory leaks. The exception info is also not passed to `__exit__`, so the span status won't reflect the error condition.
|
|
[LDK-JQQ] Spans not closed on exception in async Redis execute_command (additional location):
sentry_sdk/integrations/redis/_sync_common.py#L135
In `_sentry_execute_command`, both `db_span` and `cache_span` are entered manually via `__enter__()` but if `await old_execute_command(self, name, *args, **kwargs)` raises an exception, the corresponding `__exit__()` calls are never executed. This causes the spans to remain open indefinitely, leading to missing telemetry data and potential memory leaks. The exception info is also not passed to `__exit__`, so the span status won't reflect the error condition.
|
|
Async resolve() missing set_op() and set_origin() for StreamedSpan:
sentry_sdk/integrations/strawberry.py#L314
In the async `resolve()` method, when `self.graphql_span` is a `StreamedSpan`, the code does not call `span.set_op(OP.GRAPHQL_RESOLVE)` or `span.set_origin(StrawberryIntegration.origin)`. However, the sync `resolve()` method (lines 357-358) correctly sets both. This inconsistency means async GraphQL resolvers will have incomplete span metadata in streaming mode, potentially affecting trace visualization and filtering in Sentry.
|
|
Converting None sample_rate to string produces "None" literal in baggage:
sentry_sdk/scope.py#L1306
`_update_sample_rate_from_segment` converts `span.sample_rate` to string without checking for None. When `_set_sampling_decision` returns early (e.g., tracing disabled, invalid sample rate, or rate is 0), `sample_rate` remains None. This causes `str(None)` = `"None"` to be stored in baggage's `sentry_items["sample_rate"]`, which propagates an invalid value through trace context that downstream services may fail to parse correctly.
|
|
Byte size tracking skipped when count-based flush threshold is reached:
sentry_sdk/_span_batcher.py#L68
When `size + 1 >= self.MAX_BEFORE_FLUSH` (line 68), the span is added to `_span_buffer` at line 66 but the function returns at line 70 without updating `_running_size`. This means the byte size of that span is never tracked. If the flush thread is delayed or another span arrives before the flush completes, the byte-size tracking will undercount the actual buffer size, potentially allowing more data to accumulate than intended before a byte-based flush.
|
|
[UM4-KSX] Byte size tracking skipped when count-based flush threshold is reached (additional location):
sentry_sdk/integrations/celery/__init__.py#L330
When `size + 1 >= self.MAX_BEFORE_FLUSH` (line 68), the span is added to `_span_buffer` at line 66 but the function returns at line 70 without updating `_running_size`. This means the byte size of that span is never tracked. If the flush thread is delayed or another span arrives before the flush completes, the byte-size tracking will undercount the actual buffer size, potentially allowing more data to accumulate than intended before a byte-based flush.
|
|
Async httpx client has inconsistent baggage handling that can cause duplicate sentry items:
sentry_sdk/integrations/httpx.py#L186
The async httpx client uses inline baggage handling (lines 186-192) that differs from the sync client's `add_sentry_baggage_to_headers()` helper. The async implementation appends baggage with `+=` without first stripping existing sentry items, while the sync client properly strips them. This can cause duplicate sentry baggage items when outgoing requests already have sentry-prefixed baggage, potentially causing parsing issues or exceeding header size limits on downstream services.
|
|
Spans not properly closed on exception in async Redis execute_command:
sentry_sdk/integrations/redis/_async_common.py#L120
The `_sentry_execute_command` function manually calls `__enter__()` and `__exit__()` on `db_span` and `cache_span`, but if `await old_execute_command()` raises an exception, neither span's `__exit__` method is called. This causes span leakage (spans never sent), scope corruption (the old span is never restored via `scope.span = old_span` in `__exit__`), and memory leaks. The fix is to wrap the command execution in a try/finally block that ensures both spans are properly exited.
|
|
[9AQ-VD8] Spans not properly closed on exception in async Redis execute_command (additional location):
sentry_sdk/integrations/redis/_sync_common.py#L135
The `_sentry_execute_command` function manually calls `__enter__()` and `__exit__()` on `db_span` and `cache_span`, but if `await old_execute_command()` raises an exception, neither span's `__exit__` method is called. This causes span leakage (spans never sent), scope corruption (the old span is never restored via `scope.span = old_span` in `__exit__`), and memory leaks. The fix is to wrap the command execution in a try/finally block that ensures both spans are properly exited.
|
|
Async resolve method in StreamedSpan path missing set_op() and set_origin() calls:
sentry_sdk/integrations/strawberry.py#L314
The async `resolve` method for StreamedSpan (lines 314-321) does not call `set_op(OP.GRAPHQL_RESOLVE)` or `set_origin(StrawberryIntegration.origin)`, while the sync `resolve` method (lines 352-362) correctly calls both. This inconsistency means async GraphQL resolver spans in streaming mode will be missing operation type and origin metadata, causing data loss in tracing.
|
|
Computed scope variable is unused in _end(), causing incorrect scope to be used for span capture:
sentry_sdk/traces.py#L438
In `_end()`, lines 398-400 compute a `scope` variable from the passed parameter, `self._scope`, or `sentry_sdk.get_current_scope()`. However, line 439 ignores this computed scope and directly calls `sentry_sdk.get_current_scope()._capture_span(self)`. This means if the span was associated with a different scope (e.g., passed via the `scope` parameter from `__exit__`), the span will be captured on the wrong scope, potentially causing incorrect event association or data loss.
|