Skip to content

Commit ddec964

Browse files
authored
feat(browser): Add option to ignore mark and measure spans (#16443)
- Adds `ignorePerformanceApiSpans` option that matches span names of `mark` and `measure` spans to a given array or RegExp - Adds unit tests - Adds integration test
1 parent d2299a2 commit ddec964

File tree

5 files changed

+195
-5
lines changed

5 files changed

+195
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [
8+
Sentry.browserTracingIntegration({
9+
ignorePerformanceApiSpans: ['measure-ignore', /mark-i/],
10+
idleTimeout: 9000,
11+
}),
12+
],
13+
tracesSampleRate: 1,
14+
});
15+
16+
performance.mark('mark-pass');
17+
performance.mark('mark-ignore');
18+
performance.measure('measure-pass');
19+
performance.measure('measure-ignore');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { Route } from '@playwright/test';
2+
import { expect } from '@playwright/test';
3+
import { sentryTest } from '../../../../utils/fixtures';
4+
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers';
5+
6+
sentryTest(
7+
'should ignore mark and measure spans that match `ignorePerformanceApiSpans`',
8+
async ({ getLocalTestUrl, page }) => {
9+
if (shouldSkipTracingTest()) {
10+
sentryTest.skip();
11+
}
12+
13+
await page.route('**/path/to/script.js', (route: Route) =>
14+
route.fulfill({ path: `${__dirname}/assets/script.js` }),
15+
);
16+
17+
const url = await getLocalTestUrl({ testDir: __dirname });
18+
19+
const transactionRequestPromise = waitForTransactionRequest(
20+
page,
21+
evt => evt.type === 'transaction' && evt.contexts?.trace?.op === 'pageload',
22+
);
23+
24+
await page.goto(url);
25+
26+
const transactionEvent = envelopeRequestParser(await transactionRequestPromise);
27+
const markAndMeasureSpans = transactionEvent.spans?.filter(({ op }) => op && ['mark', 'measure'].includes(op));
28+
29+
expect(markAndMeasureSpans?.length).toBe(3);
30+
expect(markAndMeasureSpans).toEqual(
31+
expect.arrayContaining([
32+
expect.objectContaining({
33+
description: 'mark-pass',
34+
op: 'mark',
35+
}),
36+
expect.objectContaining({
37+
description: 'measure-pass',
38+
op: 'measure',
39+
}),
40+
expect.objectContaining({
41+
description: 'sentry-tracing-init',
42+
op: 'mark',
43+
}),
44+
]),
45+
);
46+
},
47+
);

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
1010
setMeasurement,
1111
spanToJSON,
12+
stringMatchesSomePattern,
1213
} from '@sentry/core';
1314
import { WINDOW } from '../types';
1415
import { trackClsAsStandaloneSpan } from './cls';
@@ -307,6 +308,15 @@ interface AddPerformanceEntriesOptions {
307308
* Default: []
308309
*/
309310
ignoreResourceSpans: Array<'resouce.script' | 'resource.css' | 'resource.img' | 'resource.other' | string>;
311+
312+
/**
313+
* Performance spans created from browser Performance APIs,
314+
* `performance.mark(...)` nand `performance.measure(...)`
315+
* with `name`s matching strings in the array will not be emitted.
316+
*
317+
* Default: []
318+
*/
319+
ignorePerformanceApiSpans: Array<string | RegExp>;
310320
}
311321

312322
/** Add performance related spans to a transaction */
@@ -346,7 +356,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
346356
case 'mark':
347357
case 'paint':
348358
case 'measure': {
349-
_addMeasureSpans(span, entry, startTime, duration, timeOrigin);
359+
_addMeasureSpans(span, entry, startTime, duration, timeOrigin, options.ignorePerformanceApiSpans);
350360

351361
// capture web vitals
352362
const firstHidden = getVisibilityWatcher();
@@ -440,7 +450,15 @@ export function _addMeasureSpans(
440450
startTime: number,
441451
duration: number,
442452
timeOrigin: number,
453+
ignorePerformanceApiSpans: AddPerformanceEntriesOptions['ignorePerformanceApiSpans'],
443454
): void {
455+
if (
456+
['mark', 'measure'].includes(entry.entryType) &&
457+
stringMatchesSomePattern(entry.name, ignorePerformanceApiSpans)
458+
) {
459+
return;
460+
}
461+
444462
const navEntry = getNavigationEntry(false);
445463
const requestTime = msToSec(navEntry ? navEntry.requestStart : 0);
446464
// Because performance.measure accepts arbitrary timestamps it can produce

packages/browser-utils/test/browser/browserMetrics.test.ts

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ describe('_addMeasureSpans', () => {
7676
const startTime = 23;
7777
const duration = 356;
7878

79-
_addMeasureSpans(span, entry, startTime, duration, timeOrigin);
79+
_addMeasureSpans(span, entry, startTime, duration, timeOrigin, []);
8080

8181
expect(spans).toHaveLength(1);
8282
expect(spanToJSON(spans[0]!)).toEqual(
@@ -112,10 +112,75 @@ describe('_addMeasureSpans', () => {
112112
const startTime = 23;
113113
const duration = -50;
114114

115-
_addMeasureSpans(span, entry, startTime, duration, timeOrigin);
115+
_addMeasureSpans(span, entry, startTime, duration, timeOrigin, []);
116116

117117
expect(spans).toHaveLength(0);
118118
});
119+
120+
it('ignores performance spans that match ignorePerformanceApiSpans', () => {
121+
const pageloadSpan = new SentrySpan({ op: 'pageload', name: '/', sampled: true });
122+
const spans: Span[] = [];
123+
124+
getClient()?.on('spanEnd', span => {
125+
spans.push(span);
126+
});
127+
128+
const entries: PerformanceEntry[] = [
129+
{
130+
entryType: 'measure',
131+
name: 'measure-pass',
132+
duration: 10,
133+
startTime: 12,
134+
toJSON: () => ({}),
135+
},
136+
{
137+
entryType: 'measure',
138+
name: 'measure-ignore',
139+
duration: 10,
140+
startTime: 12,
141+
toJSON: () => ({}),
142+
},
143+
{
144+
entryType: 'mark',
145+
name: 'mark-pass',
146+
duration: 0,
147+
startTime: 12,
148+
toJSON: () => ({}),
149+
},
150+
{
151+
entryType: 'mark',
152+
name: 'mark-ignore',
153+
duration: 0,
154+
startTime: 12,
155+
toJSON: () => ({}),
156+
},
157+
{
158+
entryType: 'paint',
159+
name: 'mark-ignore',
160+
duration: 0,
161+
startTime: 12,
162+
toJSON: () => ({}),
163+
},
164+
];
165+
166+
const timeOrigin = 100;
167+
const startTime = 23;
168+
const duration = 356;
169+
170+
entries.forEach(e => {
171+
_addMeasureSpans(pageloadSpan, e, startTime, duration, timeOrigin, ['measure-i', /mark-ign/]);
172+
});
173+
174+
expect(spans).toHaveLength(3);
175+
expect(spans.map(spanToJSON)).toEqual(
176+
expect.arrayContaining([
177+
expect.objectContaining({ description: 'measure-pass', op: 'measure' }),
178+
expect.objectContaining({ description: 'mark-pass', op: 'mark' }),
179+
// name matches but type is not (mark|measure) => should not be ignored
180+
expect.objectContaining({ description: 'mark-ignore', op: 'paint' }),
181+
]),
182+
);
183+
});
119184
});
120185

121186
describe('_addResourceSpans', () => {

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,42 @@ export interface BrowserTracingOptions {
149149
*
150150
* Default: []
151151
*/
152-
ignoreResourceSpans: Array<string>;
152+
ignoreResourceSpans: Array<'resouce.script' | 'resource.css' | 'resource.img' | 'resource.other' | string>;
153+
154+
/**
155+
* Spans created from the following browser Performance APIs,
156+
*
157+
* - [`performance.mark(...)`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark)
158+
* - [`performance.measure(...)`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure)
159+
*
160+
* will not be emitted if their names match strings in this array.
161+
*
162+
* This is useful, if you come across `mark` or `measure` spans in your Sentry traces
163+
* that you want to ignore. For example, sometimes, browser extensions or libraries
164+
* emit these entries on their own, which might not be relevant to your application.
165+
*
166+
* * @example
167+
* ```ts
168+
* Sentry.init({
169+
* integrations: [
170+
* Sentry.browserTracingIntegration({
171+
* ignorePerformanceApiSpans: ['myMeasurement', /myMark/],
172+
* }),
173+
* ],
174+
* });
175+
*
176+
* // no spans will be created for these:
177+
* performance.mark('myMark');
178+
* performance.measure('myMeasurement');
179+
*
180+
* // spans will be created for these:
181+
* performance.mark('authenticated');
182+
* performance.measure('input-duration', ...);
183+
* ```
184+
*
185+
* Default: [] - By default, all `mark` and `measure` entries are sent as spans.
186+
*/
187+
ignorePerformanceApiSpans: Array<string | RegExp>;
153188

154189
/**
155190
* Link the currently started trace to a previous trace (e.g. a prior pageload, navigation or
@@ -234,6 +269,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
234269
enableLongAnimationFrame: true,
235270
enableInp: true,
236271
ignoreResourceSpans: [],
272+
ignorePerformanceApiSpans: [],
237273
linkPreviousTrace: 'in-memory',
238274
consistentTraceSampling: false,
239275
_experiments: {},
@@ -277,6 +313,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
277313
shouldCreateSpanForRequest,
278314
enableHTTPTimings,
279315
ignoreResourceSpans,
316+
ignorePerformanceApiSpans,
280317
instrumentPageLoad,
281318
instrumentNavigation,
282319
linkPreviousTrace,
@@ -319,7 +356,11 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
319356
// This will generally always be defined here, because it is set in `setup()` of the integration
320357
// but technically, it is optional, so we guard here to be extra safe
321358
_collectWebVitals?.();
322-
addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans, ignoreResourceSpans });
359+
addPerformanceEntries(span, {
360+
recordClsOnPageloadSpan: !enableStandaloneClsSpans,
361+
ignoreResourceSpans,
362+
ignorePerformanceApiSpans,
363+
});
323364
setActiveIdleSpan(client, undefined);
324365

325366
// A trace should stay consistent over the entire timespan of one route - even after the pageload/navigation ended.

0 commit comments

Comments
 (0)