[do not merge] feat: Span streaming & new span API #16216
Annotations
5 errors and 13 warnings
|
Uninitialized span_ctx causes UnboundLocalError when exception occurs mid-block:
sentry_sdk/integrations/celery/__init__.py#L339
If an exception occurs after `transaction` is assigned (line 332) but before `span_ctx` is assigned (line 339), `capture_internal_exceptions()` swallows the exception. Since `transaction is None` check (line 365) passes and `span_ctx` is never initialized, line 368 `with span_ctx:` raises `UnboundLocalError`. This can happen if `set_origin()`, `set_source()`, or `set_op()` raises an exception.
|
|
[PDZ-GA8] Uninitialized span_ctx causes UnboundLocalError when exception occurs mid-block (additional location):
sentry_sdk/scope.py#L828
If an exception occurs after `transaction` is assigned (line 332) but before `span_ctx` is assigned (line 339), `capture_internal_exceptions()` swallows the exception. Since `transaction is None` check (line 365) passes and `span_ctx` is never initialized, line 368 `with span_ctx:` raises `UnboundLocalError`. This can happen if `set_origin()`, `set_source()`, or `set_op()` raises an exception.
|
|
[PDZ-GA8] Uninitialized span_ctx causes UnboundLocalError when exception occurs mid-block (additional location):
sentry_sdk/traces.py#L670
If an exception occurs after `transaction` is assigned (line 332) but before `span_ctx` is assigned (line 339), `capture_internal_exceptions()` swallows the exception. Since `transaction is None` check (line 365) passes and `span_ctx` is never initialized, line 368 `with span_ctx:` raises `UnboundLocalError`. This can happen if `set_origin()`, `set_source()`, or `set_op()` raises an exception.
|
|
StreamedSpan not started before finish() is called, causing silent failure:
sentry_sdk/integrations/graphene.py#L151
In span streaming mode, the span is created via `sentry_sdk.traces.start_span()` but never started (no `.start()` call or context manager entry). When `finish()` is called in the finally block, it attempts to access `_context_manager_state` which was never set because `__enter__` was never called. This causes an `AttributeError` that is silently caught by `capture_internal_exceptions()`, resulting in the span never being properly sent to Sentry.
|
|
[EU2-N2V] StreamedSpan not started before finish() is called, causing silent failure (additional location):
sentry_sdk/integrations/anthropic.py#L614
In span streaming mode, the span is created via `sentry_sdk.traces.start_span()` but never started (no `.start()` call or context manager entry). When `finish()` is called in the finally block, it attempts to access `_context_manager_state` which was never set because `__enter__` was never called. This causes an `AttributeError` that is silently caught by `capture_internal_exceptions()`, resulting in the span never being properly sent to Sentry.
|
|
Size estimation returns 0 for spans under 1KB, defeating size-based flushing:
sentry_sdk/_span_batcher.py#L81
The `_estimate_size` method uses integer division by 1024 (`// 1024`), which means any span serializing to less than 1024 characters will return 0. Typical spans are likely to be a few hundred bytes, so most will contribute 0 to `_running_size`, causing the size-based flush threshold (`MAX_KB_BEFORE_FLUSH`) to effectively never trigger. The buffer could accumulate significant memory before the count-based flush (1000 spans) kicks in.
|
|
[GED-F6D] Size estimation returns 0 for spans under 1KB, defeating size-based flushing (additional location):
sentry_sdk/traces.py#L418
The `_estimate_size` method uses integer division by 1024 (`// 1024`), which means any span serializing to less than 1024 characters will return 0. Typical spans are likely to be a few hundred bytes, so most will contribute 0 to `_running_size`, causing the size-based flush threshold (`MAX_KB_BEFORE_FLUSH`) to effectively never trigger. The buffer could accumulate significant memory before the count-based flush (1000 spans) kicks in.
|
|
Control flow exceptions incorrectly marked as errors in StreamedSpan mode:
sentry_sdk/integrations/celery/__init__.py#L104
The `_set_status` function ignores the `status` parameter when using StreamedSpan and always sets `SpanStatus.ERROR`. This is incorrect when called with `"aborted"` (for Celery control flow exceptions like Retry, Ignore, Reject), which are not actual errors. The result is that legitimate task retries and intentional ignores will be incorrectly flagged as errors in Sentry traces when using span streaming mode.
|
|
[RPV-ZBA] Control flow exceptions incorrectly marked as errors in StreamedSpan mode (additional location):
sentry_sdk/integrations/celery/__init__.py#L332
The `_set_status` function ignores the `status` parameter when using StreamedSpan and always sets `SpanStatus.ERROR`. This is incorrect when called with `"aborted"` (for Celery control flow exceptions like Retry, Ignore, Reject), which are not actual errors. The result is that legitimate task retries and intentional ignores will be incorrectly flagged as errors in Sentry traces when using span streaming mode.
|
|
Missing @wraps(f) decorator breaks celery-once compatibility:
sentry_sdk/integrations/celery/__init__.py#L400
The `@ensure_integration_enabled(CeleryIntegration, f)` decorator was removed from `_inner` but the required `@wraps(f)` decorator was not added. The comment on lines 395-399 explicitly warns: "functools.wraps is important here because celery-once looks at this method's name... if we ever remove the @ensure_integration_enabled decorator, we need to add @functools.wraps(f) here." Without `@wraps(f)`, the wrapped function will have the name `_inner` instead of the original function name, breaking celery-once and potentially other tools that inspect function metadata.
|
|
Async httpx client doesn't strip existing Sentry baggage before appending:
sentry_sdk/integrations/httpx.py#L186
The async httpx implementation uses simple string concatenation to append baggage headers (line 190: `request.headers[key] += "," + value`), while the sync implementation uses `add_sentry_baggage_to_headers()` which strips existing Sentry baggage items first. This inconsistency can lead to duplicate or conflicting Sentry baggage items being sent in outgoing requests when using the async client, potentially causing issues with distributed tracing.
|
|
set_origin uses hardcoded literal "origin" instead of self.origin:
sentry_sdk/integrations/rust_tracing.py#L220
On line 220, when creating a StreamedSpan with a parent StreamedSpan, the code calls `sentry_span.set_origin("origin")` with a hardcoded string literal instead of `self.origin`. All other code paths correctly use `self.origin` (e.g., lines 225, 235, 240). This causes spans created via the Rust tracing integration in streaming mode with a parent span to have incorrect origin metadata, breaking span attribution and filtering.
|
|
Parsing span missing parent_span parameter in streaming mode:
sentry_sdk/integrations/strawberry.py#L261
In `on_parse()`, when span streaming is enabled, the parsing span is created without passing `parent_span=self.graphql_span`, unlike `on_validate()` which correctly passes it. This will cause the parsing span to be orphaned from the graphql_span tree, resulting in incorrect span hierarchy and potentially broken trace visualization in Sentry.
|
|
[93L-2PF] Parsing span missing parent_span parameter in streaming mode (additional location):
sentry_sdk/integrations/strawberry.py#L314
In `on_parse()`, when span streaming is enabled, the parsing span is created without passing `parent_span=self.graphql_span`, unlike `on_validate()` which correctly passes it. This will cause the parsing span to be orphaned from the graphql_span tree, resulting in incorrect span hierarchy and potentially broken trace visualization in Sentry.
|
|
Missing f-string prefix causes span name to be literal '{field_path}' instead of actual field path:
sentry_sdk/integrations/strawberry.py#L354
In `SentrySyncExtension.resolve()`, line 354 uses `name="resolving {field_path}"` instead of `name=f"resolving {field_path}"`. This means the span name will literally be "resolving {field_path}" rather than interpolating the actual field path value (e.g., "resolving User.name"). This affects observability and debugging since all resolver spans will have the same unhelpful name.
|
|
[RB3-J3M] Missing f-string prefix causes span name to be literal '{field_path}' instead of actual field path (additional location):
sentry_sdk/tracing_utils.py#L816
In `SentrySyncExtension.resolve()`, line 354 uses `name="resolving {field_path}"` instead of `name=f"resolving {field_path}"`. This means the span name will literally be "resolving {field_path}" rather than interpolating the actual field path value (e.g., "resolving User.name"). This affects observability and debugging since all resolver spans will have the same unhelpful name.
|
|
Empty dict in ignore_spans config matches all spans:
sentry_sdk/tracing_utils.py#L1498
In the `is_ignored_span` function, when processing dict rules, both `name_matches` and `attributes_match` default to `True` (lines 1499-1500). If a user provides an empty dict `{}` or a dict without `name` or `attributes` keys in the `ignore_spans` config, the condition `if name_matches and attributes_match` at line 1516 will be true, causing ALL spans to be ignored. The `IgnoreSpansContext` TypedDict allows empty dicts since `total=False` makes both fields optional.
|
|
Test assertion compares segment1['trace_id'] to itself instead of segment2:
tests/tracing/test_span_streaming.py#L500
In test_sibling_segments, line 500 asserts `segment1["trace_id"] == segment1["trace_id"]`, which is a tautology (always true) and doesn't verify the intended behavior. Based on the test name 'sibling_segments' and the comparison in test_sibling_segments_new_trace (line 535) which correctly uses `segment1["trace_id"] != segment2["trace_id"]`, the intent here was likely to verify that sibling segments share the same trace_id.
|