-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(node): Fix baggage propagation #11363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Bundle ReportChanges will decrease total bundle size by 3.2MB ⬇️
|
// TODO(v8): Fix this test | ||
// eslint-disable-next-line jest/no-disabled-tests | ||
test.skip('Should populate Sentry and ignore 3rd party content if sentry-trace header does not exist', async () => { | ||
test('Should populate Sentry and ignore 3rd party content if sentry-trace header does not exist', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe I'm missunderstanding the test but looking at the assertions below, shouldn't we also check for the presence of foo=bar,bar=baz
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I think this is fine; We should only care about not overwriting baggage entries that were already added to an outgoing request before we inject our entries. The foo
and bar
entries were not added further; when the outgoing request was made, right?
/** | ||
* Will parse a baggage header into an array of tuples, | ||
* where the first item is the baggage name, the second the baggage value. | ||
*/ | ||
function parseBaggageHeaderString(baggageHeader: string): [string, string][] { | ||
return baggageHeader.split(',').map(baggageEntry => { | ||
const [key, value] = baggageEntry.split('='); | ||
return [decodeURIComponent(key.trim()), decodeURIComponent(value.trim())]; | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: Is there a reason we need to parse this into an array of tuples? just wondering if it makes sense to unify things here with baggageHeaderToObject
from @sentry/utils
?
Feel free to disregard if N/A :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that makes sense!
@@ -227,6 +225,8 @@ function getContext(scope: Scope | undefined, forceTransaction: boolean | undefi | |||
const { spanId, traceId } = parentSpan.spanContext(); | |||
const sampled = getSamplingDecision(parentSpan.spanContext()); | |||
|
|||
// In this case, when we are forcing a transaction, we want to treat this like continuing an incoming trace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m: just to understand this correctly: Here we're populating a DSC at span creation time - right? Do we still update the DSC's transaction
property when the name changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not allowed to update the transaction name in that case because it's a transaction boundary and would break dynamic sampling. Or do you mean, when the parent transaciton's name updates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this case yes, we would not update this anymore - BUT it is very likely that by this time we already have updated the span correctly, so it should be fine in 99% of cases (as this will be "frozen" at the time when the child span will be created, by which time the parent span has hopefully already "stabilized") - does this make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, we should probably not use the trace state but instead use the TracePropagationContext
as single source of truth, but otherwise this looks good to me.
size-limit report 📦
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thx for explaining!
4451405
to
5237667
Compare
This depends on #11363 being merged first...
The node integration tests (correctly!) showed that we were not propagating baggage 100% correct anymore. Turns out there were a few issues with what we did before: ## Do not freeze DSC onto span `traceState` when not continuing incoming trace The main issue was, that we did not actually set the `transaction`, `sampled` and `sample_rate` baggage entries. This was the case because we only picked these from the trace state, but we only set this fully in our own `startSpan()` APIs, not when auto instrumentation created spans. So I changed this to _not_ set the trace state in `startSpan()` APIs, but instead leave it empty _unless_ we are actually continuing an incoming trace. Whenever we are _not_ continuing an incoming trace, we'll now generate the DSC at injection-time based of the actual span data. So to summarize, we now set the `dsc` trace state in two places: 1. In the propagator, when we `extract` an incoming trace. 2. In our trace functions, when we use `forceTransaction` which we treat as continuing an incoming trace. ## No merging of existing baggage headers Another issue that came up is that we did not properly handled other `baggage` entires being set. This is because of how OTEL does injection, where it passes the `carrier` as object which we treated as `unknown` and basically ignored. This meant that if a user did e.g. `fetch({ headers: { baggage: 'my=value' }})`, we would overwrite the baggage and not keep the exiting value(s). This PR fixes this as well by ensuring we keep this around. ## Future improvements After talking with @lforst, we may be able to improve/streamline this further by getting rid of the DSC trace state. The logic could be: 1. If there is a DSC on the scope's propagation context, use that. 2. Else, use the root span We may update to this logic internally in a follow up step!
This depends on getsentry#11363 being merged first...
The node integration tests (correctly!) showed that we were not propagating baggage 100% correct anymore. Turns out there were a few issues with what we did before:
Do not freeze DSC onto span
traceState
when not continuing incoming traceThe main issue was, that we did not actually set the
transaction
,sampled
andsample_rate
baggage entries. This was the case because we only picked these from the trace state, but we only set this fully in our ownstartSpan()
APIs, not when auto instrumentation created spans. So I changed this to not set the trace state instartSpan()
APIs, but instead leave it empty unless we are actually continuing an incoming trace. Whenever we are not continuing an incoming trace, we'll now generate the DSC at injection-time based of the actual span data.So to summarize, we now set the
dsc
trace state in two places:extract
an incoming trace.forceTransaction
which we treat as continuing an incoming trace.No merging of existing baggage headers
Another issue that came up is that we did not properly handled other
baggage
entires being set. This is because of how OTEL does injection, where it passes thecarrier
as object which we treated asunknown
and basically ignored. This meant that if a user did e.g.fetch({ headers: { baggage: 'my=value' }})
, we would overwrite the baggage and not keep the exiting value(s). This PR fixes this as well by ensuring we keep this around.Future improvements
After talking with @lforst, we may be able to improve/streamline this further by getting rid of the DSC trace state.
The logic could be:
We may update to this logic internally in a follow up step!