Skip to content

Commit c297bf9

Browse files
bcoeLms24
andauthored
feat(browser): option to ignore certain resource types (#16389)
Allows specific resource spans to be ignored by passing an array of strings to ignoreResourceSpans. Example values include, resource.css, resource.script. Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io>
1 parent e46c619 commit c297bf9

File tree

6 files changed

+117
-3
lines changed

6 files changed

+117
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(() => {
2+
// I do nothing.
3+
})();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
ignoreResourceSpans: ['resource.script'],
10+
idleTimeout: 9000,
11+
}),
12+
],
13+
tracesSampleRate: 1,
14+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Route } from '@playwright/test';
2+
import { expect } from '@playwright/test';
3+
import type { Event } from '@sentry/core';
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers';
6+
7+
sentryTest('should allow specific types of resource spans to be ignored.', async ({ getLocalTestUrl, page }) => {
8+
if (shouldSkipTracingTest()) {
9+
sentryTest.skip();
10+
}
11+
12+
await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` }));
13+
14+
const url = await getLocalTestUrl({ testDir: __dirname });
15+
16+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
17+
const allSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource.script'));
18+
19+
expect(allSpans?.length).toBe(0);
20+
});

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,13 @@ interface AddPerformanceEntriesOptions {
300300
* sent as a standalone span instead.
301301
*/
302302
recordClsOnPageloadSpan: boolean;
303+
304+
/**
305+
* Resource spans with `op`s matching strings in the array will not be emitted.
306+
*
307+
* Default: []
308+
*/
309+
ignoreResourceSpans: Array<'resouce.script' | 'resource.css' | 'resource.img' | 'resource.other' | string>;
303310
}
304311

305312
/** Add performance related spans to a transaction */
@@ -355,7 +362,15 @@ export function addPerformanceEntries(span: Span, options: AddPerformanceEntries
355362
break;
356363
}
357364
case 'resource': {
358-
_addResourceSpans(span, entry as PerformanceResourceTiming, entry.name, startTime, duration, timeOrigin);
365+
_addResourceSpans(
366+
span,
367+
entry as PerformanceResourceTiming,
368+
entry.name,
369+
startTime,
370+
duration,
371+
timeOrigin,
372+
options.ignoreResourceSpans,
373+
);
359374
break;
360375
}
361376
// Ignore other entry types.
@@ -568,13 +583,19 @@ export function _addResourceSpans(
568583
startTime: number,
569584
duration: number,
570585
timeOrigin: number,
586+
ignoreResourceSpans?: Array<string>,
571587
): void {
572588
// we already instrument based on fetch and xhr, so we don't need to
573589
// duplicate spans here.
574590
if (entry.initiatorType === 'xmlhttprequest' || entry.initiatorType === 'fetch') {
575591
return;
576592
}
577593

594+
const op = entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other';
595+
if (ignoreResourceSpans?.includes(op)) {
596+
return;
597+
}
598+
578599
const parsedUrl = parseUrl(resourceUrl);
579600

580601
const attributes: SpanAttributes = {
@@ -616,7 +637,7 @@ export function _addResourceSpans(
616637

617638
startAndEndSpan(span, startTimestamp, endTimestamp, {
618639
name: resourceUrl.replace(WINDOW.location.origin, ''),
619-
op: entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other',
640+
op,
620641
attributes,
621642
});
622643
}

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,53 @@ describe('_addResourceSpans', () => {
271271
}
272272
});
273273

274+
it('allows resource spans to be ignored via ignoreResourceSpans', () => {
275+
const spans: Span[] = [];
276+
const ignoredResourceSpans = ['resource.other', 'resource.script'];
277+
278+
getClient()?.on('spanEnd', span => {
279+
spans.push(span);
280+
});
281+
282+
const table = [
283+
{
284+
initiatorType: undefined,
285+
op: 'resource.other',
286+
},
287+
{
288+
initiatorType: 'css',
289+
op: 'resource.css',
290+
},
291+
{
292+
initiatorType: 'css',
293+
op: 'resource.css',
294+
},
295+
{
296+
initiatorType: 'image',
297+
op: 'resource.image',
298+
},
299+
{
300+
initiatorType: 'script',
301+
op: 'resource.script',
302+
},
303+
];
304+
for (const row of table) {
305+
const { initiatorType } = row;
306+
const entry = mockPerformanceResourceTiming({
307+
initiatorType,
308+
nextHopProtocol: 'http/1.1',
309+
});
310+
_addResourceSpans(span, entry, 'https://example.com/assets/to/me', 123, 234, 465, ignoredResourceSpans);
311+
}
312+
expect(spans).toHaveLength(table.length - ignoredResourceSpans.length);
313+
const spanOps = new Set(
314+
spans.map(s => {
315+
return spanToJSON(s).op;
316+
}),
317+
);
318+
expect(spanOps).toEqual(new Set(['resource.css', 'resource.image']));
319+
});
320+
274321
it('allows for enter size of 0', () => {
275322
const spans: Span[] = [];
276323

packages/browser/src/tracing/browserTracingIntegration.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ export interface BrowserTracingOptions {
144144
*/
145145
enableHTTPTimings: boolean;
146146

147+
/**
148+
* Resource spans with `op`s matching strings in the array will not be emitted.
149+
*
150+
* Default: []
151+
*/
152+
ignoreResourceSpans: Array<string>;
153+
147154
/**
148155
* Link the currently started trace to a previous trace (e.g. a prior pageload, navigation or
149156
* manually started span). When enabled, this option will allow you to navigate between traces
@@ -226,6 +233,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = {
226233
enableLongTask: true,
227234
enableLongAnimationFrame: true,
228235
enableInp: true,
236+
ignoreResourceSpans: [],
229237
linkPreviousTrace: 'in-memory',
230238
consistentTraceSampling: false,
231239
_experiments: {},
@@ -268,6 +276,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
268276
trackFetchStreamPerformance,
269277
shouldCreateSpanForRequest,
270278
enableHTTPTimings,
279+
ignoreResourceSpans,
271280
instrumentPageLoad,
272281
instrumentNavigation,
273282
linkPreviousTrace,
@@ -310,7 +319,7 @@ export const browserTracingIntegration = ((_options: Partial<BrowserTracingOptio
310319
// This will generally always be defined here, because it is set in `setup()` of the integration
311320
// but technically, it is optional, so we guard here to be extra safe
312321
_collectWebVitals?.();
313-
addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans });
322+
addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans, ignoreResourceSpans });
314323
setActiveIdleSpan(client, undefined);
315324

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

0 commit comments

Comments
 (0)