Skip to content

Commit a011992

Browse files
committed
ensure propagation context is handled nicely
1 parent 1434bb1 commit a011992

File tree

4 files changed

+146
-62
lines changed

4 files changed

+146
-62
lines changed

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
1717
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
1818
TRACING_DEFAULTS,
19+
addNonEnumerableProperty,
1920
browserPerformanceTimeOrigin,
2021
generateTraceId,
2122
getActiveSpan,
@@ -246,7 +247,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
246247
};
247248

248249
/** Create routing idle transaction. */
249-
function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions): Span {
250+
function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions): void {
250251
const isPageloadTransaction = startSpanOptions.op === 'pageload';
251252

252253
const finalStartSpanOptions: StartSpanOptions = beforeStartSpan
@@ -274,6 +275,15 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
274275
beforeSpanEnd: span => {
275276
_collectWebVitals();
276277
addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans });
278+
279+
// Ensure that DSC is updated with possibly final transaction etc.
280+
const scope = getCurrentScope();
281+
const oldPropagationContext = scope.getPropagationContext();
282+
283+
scope.setPropagationContext({
284+
...oldPropagationContext,
285+
dsc: getDynamicSamplingContextFromSpan(span),
286+
});
277287
},
278288
});
279289

@@ -291,16 +301,31 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
291301
emitFinish();
292302
}
293303

294-
return idleSpan;
304+
// A trace should to stay the consistent over the entire time span of one route.
305+
// Therefore, when the initial pageload or navigation root span ends, we update the
306+
// scope's propagation context to keep span-specific attributes like the `sampled` decision and
307+
// the dynamic sampling context valid, even after the root span has ended.
308+
// This ensures that the trace data is consistent for the entire duration of the route.
309+
const scope = getCurrentScope();
310+
const oldPropagationContext = scope.getPropagationContext();
311+
312+
scope.setPropagationContext({
313+
...oldPropagationContext,
314+
traceId: idleSpan.spanContext().traceId,
315+
sampled: spanIsSampled(idleSpan),
316+
dsc: getDynamicSamplingContextFromSpan(idleSpan),
317+
});
318+
319+
setActiveIdleSpan(client, idleSpan);
295320
}
296321

297322
return {
298323
name: BROWSER_TRACING_INTEGRATION_ID,
299324
afterAllSetup(client) {
300-
let activeSpan: Span | undefined;
301325
let startingUrl: string | undefined = WINDOW.location && WINDOW.location.href;
302326

303327
function maybeEndActiveSpan(): void {
328+
const activeSpan = getActiveIdleSpan(client);
304329
if (activeSpan && !spanToJSON(activeSpan).timestamp) {
305330
DEBUG_BUILD && logger.log(`[Tracing] Finishing current active span with op: ${spanToJSON(activeSpan).op}`);
306331
// If there's an open active span, we need to finish it before creating an new one.
@@ -315,7 +340,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
315340

316341
maybeEndActiveSpan();
317342

318-
activeSpan = _createRouteSpan(client, {
343+
_createRouteSpan(client, {
319344
op: 'navigation',
320345
...startSpanOptions,
321346
});
@@ -333,33 +358,12 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
333358
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
334359
getCurrentScope().setPropagationContext(propagationContext);
335360

336-
activeSpan = _createRouteSpan(client, {
361+
_createRouteSpan(client, {
337362
op: 'pageload',
338363
...startSpanOptions,
339364
});
340365
});
341366

342-
// A trace should to stay the consistent over the entire time span of one route.
343-
// Therefore, when the initial pageload or navigation root span ends, we update the
344-
// scope's propagation context to keep span-specific attributes like the `sampled` decision and
345-
// the dynamic sampling context valid, even after the root span has ended.
346-
// This ensures that the trace data is consistent for the entire duration of the route.
347-
client.on('spanEnd', span => {
348-
const op = spanToJSON(span).op;
349-
if (span !== getRootSpan(span) || (op !== 'navigation' && op !== 'pageload')) {
350-
return;
351-
}
352-
353-
const scope = getCurrentScope();
354-
const oldPropagationContext = scope.getPropagationContext();
355-
356-
scope.setPropagationContext({
357-
...oldPropagationContext,
358-
sampled: oldPropagationContext.sampled !== undefined ? oldPropagationContext.sampled : spanIsSampled(span),
359-
dsc: oldPropagationContext.dsc || getDynamicSamplingContextFromSpan(span),
360-
});
361-
});
362-
363367
if (WINDOW.location) {
364368
if (instrumentPageLoad) {
365369
startBrowserTracingPageLoadSpan(client, {
@@ -440,12 +444,9 @@ export function startBrowserTracingPageLoadSpan(
440444
traceOptions?: { sentryTrace?: string | undefined; baggage?: string | undefined },
441445
): Span | undefined {
442446
client.emit('startPageLoadSpan', spanOptions, traceOptions);
443-
444447
getCurrentScope().setTransactionName(spanOptions.name);
445448

446-
const span = getActiveSpan();
447-
const op = span && spanToJSON(span).op;
448-
return op === 'pageload' ? span : undefined;
449+
return getActiveIdleSpan(client);
449450
}
450451

451452
/**
@@ -454,15 +455,11 @@ export function startBrowserTracingPageLoadSpan(
454455
*/
455456
export function startBrowserTracingNavigationSpan(client: Client, spanOptions: StartSpanOptions): Span | undefined {
456457
getIsolationScope().setPropagationContext({ traceId: generateTraceId() });
457-
getCurrentScope().setPropagationContext({ traceId: generateTraceId() });
458458

459459
client.emit('startNavigationSpan', spanOptions);
460-
461460
getCurrentScope().setTransactionName(spanOptions.name);
462461

463-
const span = getActiveSpan();
464-
const op = span && spanToJSON(span).op;
465-
return op === 'navigation' ? span : undefined;
462+
return getActiveIdleSpan(client);
466463
}
467464

468465
/** Returns the value of a meta tag */
@@ -536,3 +533,13 @@ function registerInteractionListener(
536533
addEventListener('click', registerInteractionTransaction, { once: false, capture: true });
537534
}
538535
}
536+
537+
// We store the active idle span on the client object, so we can access it from exported functions
538+
const ACTIVE_IDLE_SPAN_PROPERTY = '_sentry_idleSpan';
539+
function getActiveIdleSpan(client: Client): Span | undefined {
540+
return (client as { [ACTIVE_IDLE_SPAN_PROPERTY]?: Span })[ACTIVE_IDLE_SPAN_PROPERTY];
541+
}
542+
543+
function setActiveIdleSpan(client: Client, span: Span): void {
544+
addNonEnumerableProperty(client, ACTIVE_IDLE_SPAN_PROPERTY, span);
545+
}

0 commit comments

Comments
 (0)