@@ -16,6 +16,7 @@ import {
16
16
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
17
17
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
18
18
TRACING_DEFAULTS ,
19
+ addNonEnumerableProperty ,
19
20
browserPerformanceTimeOrigin ,
20
21
generateTraceId ,
21
22
getActiveSpan ,
@@ -246,7 +247,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
246
247
} ;
247
248
248
249
/** Create routing idle transaction. */
249
- function _createRouteSpan ( client : Client , startSpanOptions : StartSpanOptions ) : Span {
250
+ function _createRouteSpan ( client : Client , startSpanOptions : StartSpanOptions ) : void {
250
251
const isPageloadTransaction = startSpanOptions . op === 'pageload' ;
251
252
252
253
const finalStartSpanOptions : StartSpanOptions = beforeStartSpan
@@ -274,6 +275,15 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
274
275
beforeSpanEnd : span => {
275
276
_collectWebVitals ( ) ;
276
277
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
+ } ) ;
277
287
} ,
278
288
} ) ;
279
289
@@ -291,16 +301,31 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
291
301
emitFinish ( ) ;
292
302
}
293
303
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 ) ;
295
320
}
296
321
297
322
return {
298
323
name : BROWSER_TRACING_INTEGRATION_ID ,
299
324
afterAllSetup ( client ) {
300
- let activeSpan : Span | undefined ;
301
325
let startingUrl : string | undefined = WINDOW . location && WINDOW . location . href ;
302
326
303
327
function maybeEndActiveSpan ( ) : void {
328
+ const activeSpan = getActiveIdleSpan ( client ) ;
304
329
if ( activeSpan && ! spanToJSON ( activeSpan ) . timestamp ) {
305
330
DEBUG_BUILD && logger . log ( `[Tracing] Finishing current active span with op: ${ spanToJSON ( activeSpan ) . op } ` ) ;
306
331
// 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
315
340
316
341
maybeEndActiveSpan ( ) ;
317
342
318
- activeSpan = _createRouteSpan ( client , {
343
+ _createRouteSpan ( client , {
319
344
op : 'navigation' ,
320
345
...startSpanOptions ,
321
346
} ) ;
@@ -333,33 +358,12 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
333
358
const propagationContext = propagationContextFromHeaders ( sentryTrace , baggage ) ;
334
359
getCurrentScope ( ) . setPropagationContext ( propagationContext ) ;
335
360
336
- activeSpan = _createRouteSpan ( client , {
361
+ _createRouteSpan ( client , {
337
362
op : 'pageload' ,
338
363
...startSpanOptions ,
339
364
} ) ;
340
365
} ) ;
341
366
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
-
363
367
if ( WINDOW . location ) {
364
368
if ( instrumentPageLoad ) {
365
369
startBrowserTracingPageLoadSpan ( client , {
@@ -440,12 +444,9 @@ export function startBrowserTracingPageLoadSpan(
440
444
traceOptions ?: { sentryTrace ?: string | undefined ; baggage ?: string | undefined } ,
441
445
) : Span | undefined {
442
446
client . emit ( 'startPageLoadSpan' , spanOptions , traceOptions ) ;
443
-
444
447
getCurrentScope ( ) . setTransactionName ( spanOptions . name ) ;
445
448
446
- const span = getActiveSpan ( ) ;
447
- const op = span && spanToJSON ( span ) . op ;
448
- return op === 'pageload' ? span : undefined ;
449
+ return getActiveIdleSpan ( client ) ;
449
450
}
450
451
451
452
/**
@@ -454,15 +455,11 @@ export function startBrowserTracingPageLoadSpan(
454
455
*/
455
456
export function startBrowserTracingNavigationSpan ( client : Client , spanOptions : StartSpanOptions ) : Span | undefined {
456
457
getIsolationScope ( ) . setPropagationContext ( { traceId : generateTraceId ( ) } ) ;
457
- getCurrentScope ( ) . setPropagationContext ( { traceId : generateTraceId ( ) } ) ;
458
458
459
459
client . emit ( 'startNavigationSpan' , spanOptions ) ;
460
-
461
460
getCurrentScope ( ) . setTransactionName ( spanOptions . name ) ;
462
461
463
- const span = getActiveSpan ( ) ;
464
- const op = span && spanToJSON ( span ) . op ;
465
- return op === 'navigation' ? span : undefined ;
462
+ return getActiveIdleSpan ( client ) ;
466
463
}
467
464
468
465
/** Returns the value of a meta tag */
@@ -536,3 +533,13 @@ function registerInteractionListener(
536
533
addEventListener ( 'click' , registerInteractionTransaction , { once : false , capture : true } ) ;
537
534
}
538
535
}
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