Skip to content

Commit d493702

Browse files
committed
feat(browser): Update propagationContext on spanEnd to keep trace consistent
1 parent dc19460 commit d493702

File tree

3 files changed

+37
-12
lines changed

3 files changed

+37
-12
lines changed

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ sentryTest(
130130
const request = await requestPromise;
131131
const headers = request.headers();
132132

133-
// sampling decision is deferred b/c of no active span at the time of request
133+
// sampling decision and DSC are continued from navigation span, even after it ended
134134
const navigationTraceId = navigationTraceContext?.trace_id;
135-
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}$`));
135+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`));
136136
expect(headers['baggage']).toEqual(
137-
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId}`,
137+
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true`,
138138
);
139139
},
140140
);
@@ -203,11 +203,11 @@ sentryTest(
203203
const request = await xhrPromise;
204204
const headers = request.headers();
205205

206-
// sampling decision is deferred b/c of no active span at the time of request
206+
// sampling decision and DSC are continued from navigation span, even after it ended
207207
const navigationTraceId = navigationTraceContext?.trace_id;
208-
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}$`));
208+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`));
209209
expect(headers['baggage']).toEqual(
210-
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId}`,
210+
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true`,
211211
);
212212
},
213213
);

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ sentryTest(
124124
const request = await requestPromise;
125125
const headers = request.headers();
126126

127-
// sampling decision is deferred b/c of no active span at the time of request
127+
// sampling decision and DSC are continued from the pageload span even after it ended
128128
const pageloadTraceId = pageloadTraceContext?.trace_id;
129-
expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}$`));
129+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`));
130130
expect(headers['baggage']).toEqual(
131-
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId}`,
131+
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true`,
132132
);
133133
},
134134
);
@@ -191,11 +191,11 @@ sentryTest(
191191
const request = await requestPromise;
192192
const headers = request.headers();
193193

194-
// sampling decision is deferred b/c of no active span at the time of request
194+
// sampling decision and DSC are continued from the pageload span even after it ended
195195
const pageloadTraceId = pageloadTraceContext?.trace_id;
196-
expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}$`));
196+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`));
197197
expect(headers['baggage']).toEqual(
198-
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId}`,
198+
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true`,
199199
);
200200
},
201201
);

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import {
1414
getActiveSpan,
1515
getClient,
1616
getCurrentScope,
17+
getDynamicSamplingContextFromSpan,
1718
getIsolationScope,
1819
getRootSpan,
1920
registerSpanErrorInstrumentation,
21+
spanIsSampled,
2022
spanToJSON,
2123
startIdleSpan,
2224
withScope,
@@ -282,6 +284,29 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
282284
});
283285
});
284286

287+
client.on('spanEnd', span => {
288+
const op = spanToJSON(span).op;
289+
if (span !== getRootSpan(span) || (op !== 'navigation' && op !== 'pageload')) {
290+
return;
291+
}
292+
293+
const scope = getCurrentScope();
294+
295+
// A trace should to stay the same for the entire time span of one route.
296+
// Therefore, when the initial pageload or navigation transaction is ended, we update the
297+
// scope's propagation context to keep span-specific attributes like the `sampled` decision and
298+
// the dynamic sampling context valid, even after the transaction has ended.
299+
// This ensures that the trace data is consistent for the entire duration of the route.
300+
const newPropagationContext = {
301+
...scope.getPropagationContext(),
302+
// it's okay to always set sampled: true/false here. If we have a span, it cannot be un-sampled.
303+
sampled: spanIsSampled(span),
304+
dsc: getDynamicSamplingContextFromSpan(span),
305+
};
306+
307+
scope.setPropagationContext(newPropagationContext);
308+
});
309+
285310
if (options.instrumentPageLoad && WINDOW.location) {
286311
const startSpanOptions: StartSpanOptions = {
287312
name: WINDOW.location.pathname,

0 commit comments

Comments
 (0)