Skip to content

Commit 0efbab7

Browse files
committed
feat(browser): Add option to ignore mark and measure spans
1 parent 6a38b86 commit 0efbab7

5 files changed

Lines changed: 178 additions & 6 deletions

File tree

Lines changed: 19 additions & 0 deletions
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://public@dsn.ingest.sentry.io/1337',
7+
integrations: [
8+
Sentry.browserTracingIntegration({
9+
ignoreMeasureSpans: ['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');
Lines changed: 47 additions & 0 deletions
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 `ignoreMeasureSpans`',
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: 15 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,14 @@ interface AddPerformanceEntriesOptions {
307308
* Default: []
308309
*/
309310
ignoreResourceSpans: Array<'resouce.script' | 'resource.css' | 'resource.img' | 'resource.other' | string>;
311+
312+
/**
313+
* Performance spans created from `performance.mark(...)` or `performance.measure(...)`
314+
* with `name`s matching strings in the array will not be emitted.
315+
*
316+
* Default: []
317+
*/
318+
ignoreMeasureSpans: Array<string>;
310319
}
311320

312321
/** Add performance related spans to a transaction */
@@ -346,7 +355,7 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
346355
case 'mark':
347356
case 'paint':
348357
case 'measure': {
349-
_addMeasureSpans(span, entry, startTime, duration, timeOrigin);
358+
_addMeasureSpans(span, entry, startTime, duration, timeOrigin, options.ignoreMeasureSpans);
350359

351360
// capture web vitals
352361
const firstHidden = getVisibilityWatcher();
@@ -440,7 +449,12 @@ export function _addMeasureSpans(
440449
startTime: number,
441450
duration: number,
442451
timeOrigin: number,
452+
ignoreMeasureSpans: Array<string>,
443453
): void {
454+
if (['mark', 'measure'].includes(entry.entryType) && stringMatchesSomePattern(entry.name, ignoreMeasureSpans)) {
455+
return;
456+
}
457+
444458
const navEntry = getNavigationEntry(false);
445459
const requestTime = msToSec(navEntry ? navEntry.requestStart : 0);
446460
// Because performance.measure accepts arbitrary timestamps it can produce

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

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import {
1010
spanToJSON,
1111
} from '@sentry/core';
1212
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
13-
import { _addMeasureSpans, _addNavigationSpans, _addResourceSpans } from '../../src/metrics/browserMetrics';
13+
import {
14+
_addMeasureSpans,
15+
_addNavigationSpans,
16+
_addResourceSpans,
17+
addPerformanceEntries,
18+
} from '../../src/metrics/browserMetrics';
1419
import { WINDOW } from '../../src/types';
1520
import { getDefaultClientOptions, TestClient } from '../utils/TestClient';
1621

@@ -76,7 +81,7 @@ describe('_addMeasureSpans', () => {
7681
const startTime = 23;
7782
const duration = 356;
7883

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

8186
expect(spans).toHaveLength(1);
8287
expect(spanToJSON(spans[0]!)).toEqual(
@@ -112,10 +117,76 @@ describe('_addMeasureSpans', () => {
112117
const startTime = 23;
113118
const duration = -50;
114119

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

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

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

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,22 @@ 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
156+
* [`performance.mark(...)`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark)
157+
* and
158+
* [`performance.measure(...)`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure)
159+
* calls will not be emitted if their names match strings in this array.
160+
*
161+
* This is useful, if you come across `mark` or `measure` spans in your Sentry traces
162+
* that you want to ignore. For example, sometimes, browser extensions or libraries
163+
* emit these entries on their own, which might not be relevant to your application.
164+
*
165+
* Default: [] - By default, all `mark` and `measure` entries are sent as spans.
166+
*/
167+
ignoreMeasureSpans: Array<string>;
153168

154169
/**
155170
* Link the currently started trace to a previous trace (e.g. a prior pageload, navigation or
@@ -234,6 +249,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
234249
enableLongAnimationFrame: true,
235250
enableInp: true,
236251
ignoreResourceSpans: [],
252+
ignoreMeasureSpans: [],
237253
linkPreviousTrace: 'in-memory',
238254
consistentTraceSampling: false,
239255
_experiments: {},
@@ -277,6 +293,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
277293
shouldCreateSpanForRequest,
278294
enableHTTPTimings,
279295
ignoreResourceSpans,
296+
ignoreMeasureSpans,
280297
instrumentPageLoad,
281298
instrumentNavigation,
282299
linkPreviousTrace,
@@ -319,7 +336,11 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
319336
// This will generally always be defined here, because it is set in `setup()` of the integration
320337
// but technically, it is optional, so we guard here to be extra safe
321338
_collectWebVitals?.();
322-
addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans, ignoreResourceSpans });
339+
addPerformanceEntries(span, {
340+
recordClsOnPageloadSpan: !enableStandaloneClsSpans,
341+
ignoreResourceSpans,
342+
ignoreMeasureSpans,
343+
});
323344
setActiveIdleSpan(client, undefined);
324345

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

0 commit comments

Comments
 (0)