Skip to content

Commit 75624ea

Browse files
committed
add pageload span id
1 parent 20cfb44 commit 75624ea

File tree

2 files changed

+37
-5
lines changed
  • dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans
  • packages/browser-utils/src/metrics

2 files changed

+37
-5
lines changed

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ sentryTest('captures a "GOOD" CLS vital with its source as a standalone span', a
6868
'sentry.origin': 'auto.http.browser.cls',
6969
transaction: expect.stringContaining('index.html'),
7070
'user_agent.original': expect.stringContaining('Chrome'),
71+
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
7172
},
7273
description: expect.stringContaining('body > div#content > p'),
7374
exclusive_time: 0,
@@ -134,6 +135,7 @@ sentryTest('captures a "MEH" CLS vital with its source as a standalone span', as
134135
'sentry.origin': 'auto.http.browser.cls',
135136
transaction: expect.stringContaining('index.html'),
136137
'user_agent.original': expect.stringContaining('Chrome'),
138+
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
137139
},
138140
description: expect.stringContaining('body > div#content > p'),
139141
exclusive_time: 0,
@@ -198,6 +200,7 @@ sentryTest('captures a "POOR" CLS vital with its source as a standalone span.',
198200
'sentry.origin': 'auto.http.browser.cls',
199201
transaction: expect.stringContaining('index.html'),
200202
'user_agent.original': expect.stringContaining('Chrome'),
203+
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
201204
},
202205
description: expect.stringContaining('body > div#content > p'),
203206
exclusive_time: 0,
@@ -263,6 +266,7 @@ sentryTest(
263266
'sentry.origin': 'auto.http.browser.cls',
264267
transaction: expect.stringContaining('index.html'),
265268
'user_agent.original': expect.stringContaining('Chrome'),
269+
'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/),
266270
},
267271
description: 'Layout shift',
268272
exclusive_time: 0,
@@ -306,6 +310,12 @@ sentryTest(
306310
expect(eventData.type).toBe('transaction');
307311
expect(eventData.contexts?.trace?.op).toBe('pageload');
308312

313+
const pageloadSpanId = eventData.contexts?.trace?.span_id;
314+
const pageloadTraceId = eventData.contexts?.trace?.trace_id;
315+
316+
expect(pageloadSpanId).toMatch(/[a-f0-9]{16}/);
317+
expect(pageloadTraceId).toMatch(/[a-f0-9]{32}/);
318+
309319
const spanEnvelopePromise = getMultipleSentryEnvelopeRequests<SpanEnvelope>(
310320
page,
311321
1,
@@ -322,6 +332,10 @@ sentryTest(
322332
// Flakey value dependent on timings -> we check for a range
323333
expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0.05);
324334
expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.15);
335+
336+
// Ensure the CLS span is connected to the pageload span and trace
337+
expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toBe(pageloadSpanId);
338+
expect(spanEnvelopeItem.trace_id).toEqual(pageloadTraceId);
325339
},
326340
);
327341

@@ -349,6 +363,7 @@ sentryTest('sends CLS of the initial page when soft-navigating to a new page', a
349363
// Flakey value dependent on timings -> we check for a range
350364
expect(spanEnvelopeItem.measurements?.cls?.value).toBeGreaterThan(0.05);
351365
expect(spanEnvelopeItem.measurements?.cls?.value).toBeLessThan(0.15);
366+
expect(spanEnvelopeItem.data?.['sentry.pageload.span_id']).toMatch(/[a-f0-9]{16}/);
352367
});
353368

354369
sentryTest("doesn't send further CLS after the first navigation", async ({ getLocalTestPath, page }) => {

packages/browser-utils/src/metrics/cls.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import {
44
SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE,
55
SEMANTIC_ATTRIBUTE_SENTRY_OP,
66
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
7+
getActiveSpan,
78
getClient,
89
getCurrentScope,
10+
getRootSpan,
11+
spanToJSON,
912
} from '@sentry/core';
1013
import type { SpanAttributes } from '@sentry/types';
1114
import { browserPerformanceTimeOrigin, dropUndefinedKeys, htmlTreeAsString, logger } from '@sentry/utils';
@@ -26,6 +29,7 @@ import { onHidden } from './web-vitals/lib/onHidden';
2629
export function trackClsAsStandaloneSpan(): void {
2730
let standaloneCLsValue = 0;
2831
let standaloneClsEntry: LayoutShift | undefined;
32+
let pageloadSpanId: string | undefined;
2933

3034
if (!supportsLayoutShift()) {
3135
return;
@@ -36,9 +40,11 @@ export function trackClsAsStandaloneSpan(): void {
3640
if (sentSpan) {
3741
return;
3842
}
39-
sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry);
40-
cleanupClsHandler();
4143
sentSpan = true;
44+
if (pageloadSpanId) {
45+
sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId);
46+
}
47+
cleanupClsHandler();
4248
}
4349

4450
const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {
@@ -59,14 +65,23 @@ export function trackClsAsStandaloneSpan(): void {
5965
// we need to wait with subscribing to a client hook until the client is created. Therefore, we defer
6066
// to the next tick after the SDK setup.
6167
setTimeout(() => {
62-
const unsubscribe = getClient()?.on('startNavigationSpan', () => {
68+
const client = getClient();
69+
70+
const unsubscribeStartNavigation = client?.on('startNavigationSpan', () => {
6371
_collectClsOnce();
64-
typeof unsubscribe === 'function' && unsubscribe();
72+
unsubscribeStartNavigation && unsubscribeStartNavigation();
6573
});
74+
75+
const activeSpan = getActiveSpan();
76+
const rootSpan = activeSpan && getRootSpan(activeSpan);
77+
const spanJSON = rootSpan && spanToJSON(rootSpan);
78+
if (spanJSON && spanJSON.op === 'pageload') {
79+
pageloadSpanId = rootSpan.spanContext().spanId;
80+
}
6681
}, 0);
6782
}
6883

69-
function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined) {
84+
function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, pageloadSpanId: string) {
7085
DEBUG_BUILD && logger.log(`Sending CLS span (${clsValue})`);
7186

7287
const startTime = msToSec(browserPerformanceTimeOrigin as number) + (entry?.startTime || 0);
@@ -82,6 +97,8 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined)
8297
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.browser.cls',
8398
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.webvital.cls',
8499
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration || 0,
100+
// attach the pageload span id to the CLS span so that we can link them in the UI
101+
'sentry.pageload.span_id': pageloadSpanId,
85102
});
86103

87104
const span = startStandaloneWebVitalSpan({

0 commit comments

Comments
 (0)