@@ -14,6 +14,7 @@ import {
14
14
GLOBAL_OBJ ,
15
15
SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON ,
16
16
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
17
+ SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE ,
17
18
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
18
19
TRACING_DEFAULTS ,
19
20
addNonEnumerableProperty ,
@@ -36,10 +37,10 @@ import { DEBUG_BUILD } from '../debug-build';
36
37
import { WINDOW } from '../helpers' ;
37
38
import { registerBackgroundTabDetection } from './backgroundtab' ;
38
39
import { defaultRequestInstrumentationOptions , instrumentOutgoingRequests } from './request' ;
39
- import type { PreviousTraceInfo } from './previousTrace' ;
40
40
import {
41
41
addPreviousTraceSpanLink ,
42
42
getPreviousTraceFromSessionStorage ,
43
+ spanContextSampled ,
43
44
storePreviousTraceInSessionStorage ,
44
45
} from './previousTrace' ;
45
46
@@ -172,6 +173,23 @@ export interface BrowserTracingOptions {
172
173
*/
173
174
linkPreviousTrace : 'in-memory' | 'session-storage' | 'off' ;
174
175
176
+ /**
177
+ * If true, Sentry will consistently sample subsequent traces based on the
178
+ * sampling decision of the initial trace. For example, if the initial page
179
+ * load trace was sampled positively, all subsequent traces (e.g. navigations)
180
+ * are also sampled positively. In case the initial trace was sampled negatively,
181
+ * all subsequent traces are also sampled negatively.
182
+ *
183
+ * This option lets you get consistent, linked traces within a user journey
184
+ * while maintaining an overall quota based on your trace sampling settings.
185
+ *
186
+ * This option is only effective if {@link BrowserTracingOptions.linkPreviousTrace}
187
+ * is enabled (i.e. not set to `'off'`).
188
+ *
189
+ * @default `false` - this is an opt-in feature.
190
+ */
191
+ sampleLinkedTracesConsistently : boolean ;
192
+
175
193
/**
176
194
* _experiments allows the user to send options to define how this integration works.
177
195
*
@@ -206,6 +224,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
206
224
enableLongAnimationFrame : true ,
207
225
enableInp : true ,
208
226
linkPreviousTrace : 'in-memory' ,
227
+ sampleLinkedTracesConsistently : false ,
209
228
_experiments : { } ,
210
229
...defaultRequestInstrumentationOptions ,
211
230
} ;
@@ -246,6 +265,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
246
265
instrumentPageLoad,
247
266
instrumentNavigation,
248
267
linkPreviousTrace,
268
+ sampleLinkedTracesConsistently,
249
269
} = {
250
270
...DEFAULT_BROWSER_TRACING_OPTIONS ,
251
271
..._options ,
@@ -322,6 +342,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
322
342
} ) ;
323
343
} ,
324
344
} ) ;
345
+
325
346
setActiveIdleSpan ( client , idleSpan ) ;
326
347
327
348
function emitFinish ( ) : void {
@@ -389,20 +410,67 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
389
410
} ) ;
390
411
391
412
if ( linkPreviousTrace !== 'off' ) {
392
- let inMemoryPreviousTraceInfo : PreviousTraceInfo | undefined = undefined ;
413
+ const useSessionStorage = linkPreviousTrace === 'session-storage' ;
414
+
415
+ let inMemoryPreviousTraceInfo = useSessionStorage ? getPreviousTraceFromSessionStorage ( ) : undefined ;
393
416
394
417
client . on ( 'spanStart' , span => {
395
418
if ( getRootSpan ( span ) !== span ) {
396
419
return ;
397
420
}
398
421
399
- if ( linkPreviousTrace === 'session-storage' ) {
400
- const updatedPreviousTraceInfo = addPreviousTraceSpanLink ( getPreviousTraceFromSessionStorage ( ) , span ) ;
401
- storePreviousTraceInSessionStorage ( updatedPreviousTraceInfo ) ;
402
- } else {
403
- inMemoryPreviousTraceInfo = addPreviousTraceSpanLink ( inMemoryPreviousTraceInfo , span ) ;
422
+ const scope = getCurrentScope ( ) ;
423
+ const oldPropagationContext = scope . getPropagationContext ( ) ;
424
+ inMemoryPreviousTraceInfo = addPreviousTraceSpanLink ( inMemoryPreviousTraceInfo , span , oldPropagationContext ) ;
425
+
426
+ if ( useSessionStorage ) {
427
+ storePreviousTraceInSessionStorage ( inMemoryPreviousTraceInfo ) ;
404
428
}
405
429
} ) ;
430
+
431
+ if ( sampleLinkedTracesConsistently ) {
432
+ /*
433
+ This is a massive hack I'm really not proud of:
434
+
435
+ When users opt into `sampleLinkedTracesConsistently`, we need to make sure that we "propagate"
436
+ the previous trace's sample rate and rand to the current trace. This is necessary because otherwise, span
437
+ metric extrapolation is off, as we'd be propagating a too high sample rate for the subsequent traces.
438
+
439
+ So therefore, we pretend that the previous trace was the parent trace of the newly started trace. To do that,
440
+ we mutate the propagation context of the current trace and set the sample rate and sample rand of the previous trace.
441
+ Timing-wise, it is fine because it happens before we even sample the root span.
442
+
443
+ @see https://github.com/getsentry/sentry-javascript/issues/15754
444
+ */
445
+ client . on ( 'beforeSampling' , mutableSamplingContextData => {
446
+ if ( ! inMemoryPreviousTraceInfo ) {
447
+ return ;
448
+ }
449
+
450
+ const scope = getCurrentScope ( ) ;
451
+ const currentPropagationContext = scope . getPropagationContext ( ) ;
452
+
453
+ scope . setPropagationContext ( {
454
+ ...currentPropagationContext ,
455
+ dsc : {
456
+ ...currentPropagationContext . dsc ,
457
+ // The fallback to 0 should never happen; this is rather to satisfy the types
458
+ sample_rate : String ( inMemoryPreviousTraceInfo . sampleRate ?? 0 ) ,
459
+ sampled : String ( spanContextSampled ( inMemoryPreviousTraceInfo . spanContext ) ) ,
460
+ } ,
461
+ sampleRand : inMemoryPreviousTraceInfo . sampleRand ,
462
+ } ) ;
463
+
464
+ mutableSamplingContextData . parentSampled = spanContextSampled ( inMemoryPreviousTraceInfo . spanContext ) ;
465
+ mutableSamplingContextData . parentSampleRate = inMemoryPreviousTraceInfo . sampleRate ;
466
+
467
+ mutableSamplingContextData . spanAttributes = {
468
+ ...mutableSamplingContextData . spanAttributes ,
469
+ // record an attribute that this span was "force-sampled", so that we can later check on this.
470
+ [ SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE ] : inMemoryPreviousTraceInfo . sampleRate ,
471
+ } ;
472
+ } ) ;
473
+ }
406
474
}
407
475
408
476
if ( WINDOW . location ) {
0 commit comments