fix(tracing): Fix unsampled/deferred trace propagation in span streaming#6757
fix(tracing): Fix unsampled/deferred trace propagation in span streaming#6757sentrivana wants to merge 24 commits into
Conversation
Codecov Results 📊✅ 91200 passed | ⏭️ 6302 skipped | Total: 97502 | Pass Rate: 93.54% | Execution Time: 312m 25s 📊 Comparison with Base Branch
All tests are passing successfully. ✅ Patch coverage is 97.22%. Project has 2444 uncovered lines. Files with missing lines (1)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 89.76% 89.77% +0.01%
==========================================
Files 193 193 —
Lines 23888 23898 +10
Branches 8250 8256 +6
==========================================
+ Hits 21442 21454 +12
- Misses 2446 2444 -2
- Partials 1353 1352 -1Generated by Codecov Action |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 46b410a. Configure here.
|
Converting to draft to address bot comments |
ericapisani
left a comment
There was a problem hiding this comment.
Haven't had a chance to look at the tests yet, but noticed a couple things on my first pass
| self._update_sample_rate(sample_rate) | ||
|
|
||
| if sampled is False: | ||
| if sampled is False or sampled is None: |
There was a problem hiding this comment.
This can also be written as to remove the extra conditional:
| if sampled is False or sampled is None: | |
| if bool(sampled): |
| "_trace_id", | ||
| "_span_id", | ||
| "_sampled", | ||
| "_segment", |
There was a problem hiding this comment.
Of these, only _sampled needs to be added as the other 3 already exist on the parent 's __slots__ and will still be accessible by instances of NoOpStreamedSpan.
As an example:
>>> class Foo():
... __slots__ = ("x")
...
>>> class Bar(Foo):
... __slots__ = ("y")
...
>>> b = Bar()
>>> b.x = "hi"
>>> b.y = "world"
>>> print(b.x)
hi
>>> b.z
Traceback (most recent call last):
File "<python-input-7>", line 1, in <module>
b.z
AttributeError: 'Bar' object has no attribute 'z'One thing to be wary of that I found when looking into the slots docs - duplicating the variable name in __slots__ as shown here creates a shadow of the parent value.
ericapisani
left a comment
There was a problem hiding this comment.
Other than the slots thing that I mentioned earlier, this LGTM

Description
The SDK didn't behave correctly in some trace propagation scenarios in stream mode.
The current SDK design was emitting headers with a deferred sampling decision if the sampling decision was negative (whether inherited or not), because it was falling back to TwP (tracing without performance) behavior too eagerly.
Additionally, some edge cases concerning
ignore_spansand its interaction with sampling were clarified and the necessary changes included in this PR.How it should work
In the following, tracing enabled means either a
traces_sample_rateor atraces_sampleris defined. Tracing disabled means both areNoneand tracing without performance is active.There are three kinds of sampling decisions: sampled (
1), unsampled (0), and deferred (``), meaning no decision.The problem and the fix
If a no-op span was active, we were falling back to the propagation context to generate propagation headers, which by default doesn't contain a sampling decision.
With this PR,
NoOpStreamedSpansnow carry their own trace and span ID information as well as the current propagation data (a snapshot of the propagation context, likeStreamedSpans) and use it to emit the correct headers. This mirrors old behavior: spans would always be created, whether they'd be sampled or not, and they would be responsible for generating propagation headers; with the fallback to the propagation context only kicking in if there is no span.I recommend checking out the new tests to see what behavior is now expected.
Edge cases
The following cases regarding
ignore_spanswere clarified and will be documented soon:sentry-traceshould have an unsampled decision (-0),parent_span_idis the ID of the segment (or any random span ID, since for all intents and purposes, the ignored segment doesn't actually exist for any downstream service)sentry-trace's sampling decision should be the segment's sampling decision;parent_span_idshould be the first non-ignored span ancestorIssues
Closes #6755
Reminders
uv run ruff.feat:,fix:,ref:,meta:)