Skip to content

Commit bfbe6a6

Browse files
committed
ref(browser): Add CLS/LCP report event attribute to standalone spans
1 parent 7b61e1b commit bfbe6a6

File tree

3 files changed

+38
-25
lines changed

3 files changed

+38
-25
lines changed

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

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
} from '@sentry/core';
1717
import { DEBUG_BUILD } from '../debug-build';
1818
import { addClsInstrumentationHandler } from './instrument';
19+
import type { WebVitalReportEvent } from './utils';
1920
import { msToSec, startStandaloneWebVitalSpan } from './utils';
2021
import { onHidden } from './web-vitals/lib/onHidden';
22+
import { runOnce } from './web-vitals/lib/runOnce';
2123

2224
/**
2325
* Starts tracking the Cumulative Layout Shift on the current page and collects the value once
@@ -37,16 +39,13 @@ export function trackClsAsStandaloneSpan(): void {
3739
return;
3840
}
3941

40-
let sentSpan = false;
41-
function _collectClsOnce() {
42-
if (sentSpan) {
43-
return;
44-
}
45-
sentSpan = true;
46-
if (pageloadSpanId) {
47-
sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId);
48-
}
49-
cleanupClsHandler();
42+
function _collectClsOnce(reportEvent: WebVitalReportEvent) {
43+
runOnce(() => {
44+
if (pageloadSpanId) {
45+
sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId, reportEvent);
46+
}
47+
cleanupClsHandler();
48+
});
5049
}
5150

5251
const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {
@@ -59,7 +58,7 @@ export function trackClsAsStandaloneSpan(): void {
5958
}, true);
6059

6160
onHidden(() => {
62-
_collectClsOnce();
61+
_collectClsOnce('pagehide');
6362
});
6463

6564
// Since the call chain of this function is synchronous and evaluates before the SDK client is created,
@@ -73,7 +72,7 @@ export function trackClsAsStandaloneSpan(): void {
7372
}
7473

7574
const unsubscribeStartNavigation = client.on('beforeStartNavigationSpan', () => {
76-
_collectClsOnce();
75+
_collectClsOnce('navigation');
7776
unsubscribeStartNavigation?.();
7877
});
7978

@@ -88,7 +87,12 @@ export function trackClsAsStandaloneSpan(): void {
8887
}, 0);
8988
}
9089

91-
function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, pageloadSpanId: string) {
90+
function sendStandaloneClsSpan(
91+
clsValue: number,
92+
entry: LayoutShift | undefined,
93+
pageloadSpanId: string,
94+
reportEvent: WebVitalReportEvent,
95+
) {
9296
DEBUG_BUILD && logger.log(`Sending CLS span (${clsValue})`);
9397

9498
const startTime = msToSec((browserPerformanceTimeOrigin() || 0) + (entry?.startTime || 0));
@@ -102,6 +106,8 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined,
102106
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry?.duration || 0,
103107
// attach the pageload span id to the CLS span so that we can link them in the UI
104108
'sentry.pageload.span_id': pageloadSpanId,
109+
// describes what triggered the web vital to be reported
110+
'sentry.report_event': reportEvent,
105111
};
106112

107113
// Add CLS sources as span attributes to help with debugging layout shifts

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import {
1616
} from '@sentry/core';
1717
import { DEBUG_BUILD } from '../debug-build';
1818
import { addLcpInstrumentationHandler } from './instrument';
19+
import type { WebVitalReportEvent } from './utils';
1920
import { msToSec, startStandaloneWebVitalSpan } from './utils';
2021
import { onHidden } from './web-vitals/lib/onHidden';
22+
import { runOnce } from './web-vitals/lib/runOnce';
2123

2224
/**
2325
* Starts tracking the Largest Contentful Paint on the current page and collects the value once
@@ -37,16 +39,13 @@ export function trackLcpAsStandaloneSpan(): void {
3739
return;
3840
}
3941

40-
let sentSpan = false;
41-
function _collectLcpOnce() {
42-
if (sentSpan) {
43-
return;
44-
}
45-
sentSpan = true;
46-
if (pageloadSpanId) {
47-
_sendStandaloneLcpSpan(standaloneLcpValue, standaloneLcpEntry, pageloadSpanId);
48-
}
49-
cleanupLcpHandler();
42+
function _collectLcpOnce(reportEvent: WebVitalReportEvent) {
43+
runOnce(() => {
44+
if (pageloadSpanId) {
45+
_sendStandaloneLcpSpan(standaloneLcpValue, standaloneLcpEntry, pageloadSpanId, reportEvent);
46+
}
47+
cleanupLcpHandler();
48+
});
5049
}
5150

5251
const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {
@@ -59,7 +58,7 @@ export function trackLcpAsStandaloneSpan(): void {
5958
}, true);
6059

6160
onHidden(() => {
62-
_collectLcpOnce();
61+
_collectLcpOnce('pagehide');
6362
});
6463

6564
// Since the call chain of this function is synchronous and evaluates before the SDK client is created,
@@ -73,7 +72,7 @@ export function trackLcpAsStandaloneSpan(): void {
7372
}
7473

7574
const unsubscribeStartNavigation = client.on('beforeStartNavigationSpan', () => {
76-
_collectLcpOnce();
75+
_collectLcpOnce('navigation');
7776
unsubscribeStartNavigation?.();
7877
});
7978

@@ -95,6 +94,7 @@ export function _sendStandaloneLcpSpan(
9594
lcpValue: number,
9695
entry: LargestContentfulPaint | undefined,
9796
pageloadSpanId: string,
97+
reportEvent: WebVitalReportEvent,
9898
) {
9999
DEBUG_BUILD && logger.log(`Sending LCP span (${lcpValue})`);
100100

@@ -109,6 +109,8 @@ export function _sendStandaloneLcpSpan(
109109
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0, // LCP is a point-in-time metric
110110
// attach the pageload span id to the LCP span so that we can link them in the UI
111111
'sentry.pageload.span_id': pageloadSpanId,
112+
// describes what triggered the web vital to be reported
113+
'sentry.report_event': reportEvent,
112114
};
113115

114116
if (entry) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import type { Integration, SentrySpan, Span, SpanAttributes, SpanTimeInput, Star
22
import { getClient, getCurrentScope, spanToJSON, startInactiveSpan, withActiveSpan } from '@sentry/core';
33
import { WINDOW } from '../types';
44

5+
export type WebVitalReportEvent = 'pagehide' | 'navigation';
6+
57
/**
68
* Checks if a given value is a valid measurement value.
79
*/
@@ -168,3 +170,6 @@ export function extractNetworkProtocol(nextHopProtocol: string): { name: string;
168170
}
169171
return { name, version };
170172
}
173+
174+
type ReportEvent = 'pagehide' | 'navigation';
175+
function createReportOnceHandler();

0 commit comments

Comments
 (0)