Skip to content

Commit 1766d4c

Browse files
authored
ref(browser): Improve active span handling for browserTracingIntegration (#14959)
Extracted this out of #14955 We used to rely on implied stuff here quite a bit, which breaks if we start returning non recording spans. Honestly this just surfaces that this is not really ideal as it is 😬 We already pass the client around there everywhere, so this PR updates this so we keep the active idle span as non-enumerable prop on the client, ensuring this is consistent and "pure".
1 parent b98d341 commit 1766d4c

File tree

1 file changed

+25
-19
lines changed

1 file changed

+25
-19
lines changed

Diff for: packages/browser/src/tracing/browserTracingIntegration.ts

+25-19
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import {
1616
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
1717
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
1818
TRACING_DEFAULTS,
19+
addNonEnumerableProperty,
1920
browserPerformanceTimeOrigin,
2021
generateTraceId,
21-
getActiveSpan,
2222
getClient,
2323
getCurrentScope,
2424
getDynamicSamplingContextFromSpan,
@@ -247,7 +247,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
247247
};
248248

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

253253
const finalStartSpanOptions: StartSpanOptions = beforeStartSpan
@@ -275,8 +275,10 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
275275
beforeSpanEnd: span => {
276276
_collectWebVitals();
277277
addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans });
278+
setActiveIdleSpan(client, undefined);
278279
},
279280
});
281+
setActiveIdleSpan(client, idleSpan);
280282

281283
function emitFinish(): void {
282284
if (optionalWindowDocument && ['interactive', 'complete'].includes(optionalWindowDocument.readyState)) {
@@ -291,17 +293,16 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
291293

292294
emitFinish();
293295
}
294-
295-
return idleSpan;
296296
}
297297

298298
return {
299299
name: BROWSER_TRACING_INTEGRATION_ID,
300300
afterAllSetup(client) {
301-
let activeSpan: Span | undefined;
302301
let startingUrl: string | undefined = getLocationHref();
303302

304303
function maybeEndActiveSpan(): void {
304+
const activeSpan = getActiveIdleSpan(client);
305+
305306
if (activeSpan && !spanToJSON(activeSpan).timestamp) {
306307
DEBUG_BUILD && logger.log(`[Tracing] Finishing current active span with op: ${spanToJSON(activeSpan).op}`);
307308
// If there's an open active span, we need to finish it before creating an new one.
@@ -316,7 +317,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
316317

317318
maybeEndActiveSpan();
318319

319-
activeSpan = _createRouteSpan(client, {
320+
_createRouteSpan(client, {
320321
op: 'navigation',
321322
...startSpanOptions,
322323
});
@@ -334,7 +335,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
334335
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
335336
getCurrentScope().setPropagationContext(propagationContext);
336337

337-
activeSpan = _createRouteSpan(client, {
338+
_createRouteSpan(client, {
338339
op: 'pageload',
339340
...startSpanOptions,
340341
});
@@ -409,7 +410,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
409410
}
410411

411412
if (enableInteractions) {
412-
registerInteractionListener(idleTimeout, finalTimeout, childSpanTimeout, latestRoute);
413+
registerInteractionListener(client, idleTimeout, finalTimeout, childSpanTimeout, latestRoute);
413414
}
414415

415416
if (enableInp) {
@@ -441,12 +442,9 @@ export function startBrowserTracingPageLoadSpan(
441442
traceOptions?: { sentryTrace?: string | undefined; baggage?: string | undefined },
442443
): Span | undefined {
443444
client.emit('startPageLoadSpan', spanOptions, traceOptions);
444-
445445
getCurrentScope().setTransactionName(spanOptions.name);
446446

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

452450
/**
@@ -461,9 +459,7 @@ export function startBrowserTracingNavigationSpan(client: Client, spanOptions: S
461459

462460
getCurrentScope().setTransactionName(spanOptions.name);
463461

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

469465
/** Returns the value of a meta tag */
@@ -480,6 +476,7 @@ export function getMetaContent(metaName: string): string | undefined {
480476

481477
/** Start listener for interaction transactions */
482478
function registerInteractionListener(
479+
client: Client,
483480
idleTimeout: BrowserTracingOptions['idleTimeout'],
484481
finalTimeout: BrowserTracingOptions['finalTimeout'],
485482
childSpanTimeout: BrowserTracingOptions['childSpanTimeout'],
@@ -495,10 +492,9 @@ function registerInteractionListener(
495492
const registerInteractionTransaction = (): void => {
496493
const op = 'ui.action.click';
497494

498-
const activeSpan = getActiveSpan();
499-
const rootSpan = activeSpan && getRootSpan(activeSpan);
500-
if (rootSpan) {
501-
const currentRootSpanOp = spanToJSON(rootSpan).op;
495+
const activeIdleSpan = getActiveIdleSpan(client);
496+
if (activeIdleSpan) {
497+
const currentRootSpanOp = spanToJSON(activeIdleSpan).op;
502498
if (['navigation', 'pageload'].includes(currentRootSpanOp as string)) {
503499
DEBUG_BUILD &&
504500
logger.warn(`[Tracing] Did not create ${op} span because a pageload or navigation span is in progress.`);
@@ -537,3 +533,13 @@ function registerInteractionListener(
537533
addEventListener('click', registerInteractionTransaction, { once: false, capture: true });
538534
}
539535
}
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 | undefined): void {
544+
addNonEnumerableProperty(client, ACTIVE_IDLE_SPAN_PROPERTY, span);
545+
}

0 commit comments

Comments
 (0)