Skip to content

ref: Support outgoing trace propagation in span first (18)#5638

Open
sentrivana wants to merge 121 commits intomasterfrom
ivana/span-first-18-trace-propagation
Open

ref: Support outgoing trace propagation in span first (18)#5638
sentrivana wants to merge 121 commits intomasterfrom
ivana/span-first-18-trace-propagation

Conversation

@sentrivana
Copy link
Contributor

@sentrivana sentrivana commented Mar 11, 2026

Couple things going on in this PR. Bear with me, this is probably the most all over the place span first PR because the outgoing trace propagation changes make mypy complain about things elsewhere in the sdk.

1. Outgoing trace propagation

Support getting trace propagation information from the span with _get_traceparent, _get_baggage, _iter_headers, etc. These mirror the old Span class to make integrating StreamedSpans with the rest of the SDK easier (since they're used throughout), with one difference: they're explicitly private, while the corresponding Span methods were public. Added aliases to them so that we can use the private methods everywhere.

There is definite clean up potential here once we get rid of the old spans and we no longer have to make the streaming span interface work with the existing helper scope methods.

2. Addressing cascading mypy issues

Now that we're officially allowing StreamedSpans to be set on the scope, a LOT of type hints need updating all over the SDK. In many places, I've added explicit guards against functionality that doesn't exist in span first mode. This should prevent folks from using the wrong APIs in the wrong SDK mode (tracing vs. static) as well as make mypy happy.

@sentrivana sentrivana changed the title ref: Support outgoing trace propagation in span first ref: Support outgoing trace propagation in span first (18) Mar 11, 2026
Base automatically changed from ivana/span-first-17-missing-data-category to master March 12, 2026 10:07
@sentrivana sentrivana marked this pull request as ready for review March 12, 2026 13:15
@sentrivana sentrivana requested a review from a team as a code owner March 12, 2026 13:15
Comment on lines +505 to +506
if not self._segment:
return
Copy link

Choose a reason for hiding this comment

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

Bug: Calling scope.iter_trace_propagation_headers() when the active span is a NoOpStreamedSpan raises an AttributeError because the _segment attribute is not initialized.
Severity: HIGH

Suggested Fix

In sentry_sdk/scope.py, add a type check within the iter_trace_propagation_headers method to ensure the span is not an instance of NoOpStreamedSpan before calling span._iter_headers(), similar to the existing guards in get_traceparent() and get_baggage().

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: sentry_sdk/traces.py#L505-L506

Potential issue: When a `NoOpStreamedSpan` is the active span on a scope, a call to
`scope.iter_trace_propagation_headers()` will raise an `AttributeError`. This occurs
because the method calls `span._iter_headers()` without a type guard to check if the
span is a `NoOpStreamedSpan`. The `NoOpStreamedSpan` inherits the `_iter_headers` method
from `StreamedSpan` but does not initialize the `_segment` attribute that the method
accesses, leading to the error. This can be triggered during an outgoing HTTP request
when tracing is enabled but the transaction is not sampled, causing an unexpected crash.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.


if has_tracing_enabled(client.options) and span is not None:
for header in span.iter_headers():
for header in span._iter_headers():
Copy link

Choose a reason for hiding this comment

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

Missing NoOpStreamedSpan guard in iter_trace_propagation_headers

High Severity

iter_trace_propagation_headers calls span._iter_headers() without guarding against NoOpStreamedSpan, unlike get_traceparent, get_baggage, and get_trace_context which all check not isinstance(self.span, NoOpStreamedSpan). When a NoOpStreamedSpan is the active span on the scope, _iter_headers() (inherited from StreamedSpan) accesses self._segment, which was never initialized in NoOpStreamedSpan.__init__, raising AttributeError. This affects integrations like aiohttp, celery, and grpc that call this method for outgoing trace propagation.

Additional Locations (1)
Fix in Cursor Fix in Web

and self._span is not None
and not isinstance(self.span, NoOpStreamedSpan)
):
return self._span._get_trace_context()
Copy link

Choose a reason for hiding this comment

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

Inconsistent self._span vs self.span in condition

Low Severity

In get_trace_context, the condition mixes self._span is not None with not isinstance(self.span, NoOpStreamedSpan). The sibling methods get_traceparent and get_baggage consistently use self.span (the property) for both checks. While functionally equivalent since the property just returns self._span, this inconsistency looks like a copy-paste oversight and is confusing to read.

Fix in Cursor Fix in Web

from collections.abc import Mapping, MutableMapping
from datetime import timedelta
from random import Random
from typing import Pattern
Copy link

Choose a reason for hiding this comment

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

Redundant Pattern import breaks Python 3.13+

High Severity

The new unconditional from typing import Pattern at line 11 runs before the existing try/except block at lines 15-19 that gracefully handles typing.Pattern removal. Since typing.Pattern was removed in Python 3.13, this unconditional import causes an ImportError at module load time, effectively breaking the entire SDK on Python 3.13+. The existing try/except block was specifically designed to handle this by preferring re.Pattern.

Fix in Cursor Fix in Web

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant