Skip to content

Commit 8613474

Browse files
authored
feat(core): Apply ignoreSpans to streamed spans (#19934)
This PR Implements applying the `ignoreSpans` option to streamed spans (previously this option had no effect). It covers both, our core/browser- as well as the OTel/Node-based implementation. See PR for details
1 parent de2e194 commit 8613474

File tree

33 files changed

+1397
-40
lines changed

33 files changed

+1397
-40
lines changed

.size-limit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ module.exports = [
255255
path: createCDNPath('bundle.tracing.logs.metrics.min.js'),
256256
gzip: false,
257257
brotli: false,
258-
limit: '133 KB',
258+
limit: '134 KB',
259259
},
260260
{
261261
name: 'CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed',
@@ -356,7 +356,7 @@ module.exports = [
356356
import: createImport('init'),
357357
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
358358
gzip: true,
359-
limit: '116 KB',
359+
limit: '117 KB',
360360
},
361361
];
362362

dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-negative/test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,14 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains
8383
timestamp: expect.any(Number),
8484
discarded_events: [
8585
{
86-
category: 'transaction',
87-
quantity: 4,
86+
category: 'span',
87+
quantity: expect.any(Number),
8888
reason: 'sample_rate',
8989
},
9090
],
9191
});
92+
// exact number depends on performance observer emissions
93+
expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(10);
9294
});
9395

9496
expect(spansReceived).toHaveLength(0);

dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta-precedence/test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains
6969
timestamp: expect.any(Number),
7070
discarded_events: [
7171
{
72-
category: 'transaction',
73-
quantity: 2,
72+
category: 'span',
73+
quantity: expect.any(Number),
7474
reason: 'sample_rate',
7575
},
7676
],
7777
});
78+
// exact number depends on performance observer emissions
79+
expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(3);
7880
});
7981

8082
await sentryTest.step('Navigate to another page with meta tags', async () => {

dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
5353
timestamp: expect.any(Number),
5454
discarded_events: [
5555
{
56-
category: 'transaction',
56+
category: 'span',
5757
quantity: 1,
5858
reason: 'sample_rate',
5959
},
@@ -76,7 +76,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
7676
timestamp: expect.any(Number),
7777
discarded_events: [
7878
{
79-
category: 'transaction',
79+
category: 'span',
8080
quantity: 1,
8181
reason: 'sample_rate',
8282
},
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [Sentry.spanStreamingIntegration()],
8+
ignoreSpans: [/ignore/, { op: 'ignored-op' }],
9+
parentSpanIsAlwaysRootSpan: false,
10+
tracesSampleRate: 1,
11+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Sentry.startSpan({ name: 'parent-span' }, () => {
2+
Sentry.startSpan({ name: 'keep-me' }, () => {});
3+
4+
// This child matches ignoreSpans —> dropped
5+
Sentry.startSpan({ name: 'ignore-child' }, () => {
6+
// dropped
7+
Sentry.startSpan({ name: 'ignore-grandchild-1' }, () => {
8+
// kept
9+
Sentry.startSpan({ name: 'great-grandchild-1' }, () => {
10+
// dropped
11+
Sentry.startSpan({ name: 'ignore-great-great-grandchild-1' }, () => {
12+
// kept
13+
Sentry.startSpan({ name: 'great-great-great-grandchild-1' }, () => {});
14+
});
15+
});
16+
});
17+
// Grandchild is reparented to 'parent-span' —> kept
18+
Sentry.startSpan({ name: 'grandchild-2' }, () => {});
19+
});
20+
21+
// both dropped
22+
Sentry.startSpan({ name: 'name-passes-but-op-not-span-1', op: 'ignored-op' }, () => {});
23+
Sentry.startSpan(
24+
// sentry.op attribute has precedence over top op argument
25+
{ name: 'name-passes-but-op-not-span-2', op: 'keep', attributes: { 'sentry.op': 'ignored-op' } },
26+
() => {},
27+
);
28+
29+
// kept
30+
Sentry.startSpan({ name: 'another-keeper' }, () => {});
31+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import {
4+
envelopeRequestParser,
5+
hidePage,
6+
shouldSkipTracingTest,
7+
testingCdnBundle,
8+
waitForClientReportRequest,
9+
} from '../../../../utils/helpers';
10+
import { waitForStreamedSpans } from '../../../../utils/spanUtils';
11+
import type { ClientReport } from '@sentry/core';
12+
13+
sentryTest('ignored child spans are dropped and their children are reparented', async ({ getLocalTestUrl, page }) => {
14+
sentryTest.skip(shouldSkipTracingTest() || testingCdnBundle());
15+
16+
const spansPromise = waitForStreamedSpans(page, spans => !!spans?.find(s => s.name === 'parent-span'));
17+
18+
const clientReportPromise = waitForClientReportRequest(page);
19+
20+
const url = await getLocalTestUrl({ testDir: __dirname });
21+
await page.goto(url);
22+
23+
const spans = await spansPromise;
24+
25+
await hidePage(page);
26+
27+
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
28+
29+
const segmentSpanId = spans.find(s => s.name === 'parent-span')?.span_id;
30+
31+
expect(spans.length).toBe(6);
32+
33+
expect(spans.some(s => s.name === 'keep-me')).toBe(true);
34+
expect(spans.some(s => s.name === 'another-keeper')).toBe(true);
35+
36+
expect(spans.some(s => s.name?.includes('ignore'))).toBe(false);
37+
38+
const greatGrandChild1 = spans.find(s => s.name === 'great-grandchild-1');
39+
const grandchild2 = spans.find(s => s.name === 'grandchild-2');
40+
const greatGreatGreatGrandChild1 = spans.find(s => s.name === 'great-great-great-grandchild-1');
41+
42+
expect(greatGrandChild1).toBeDefined();
43+
expect(grandchild2).toBeDefined();
44+
expect(greatGreatGreatGrandChild1).toBeDefined();
45+
46+
expect(greatGrandChild1?.parent_span_id).toBe(segmentSpanId);
47+
expect(grandchild2?.parent_span_id).toBe(segmentSpanId);
48+
expect(greatGreatGreatGrandChild1?.parent_span_id).toBe(greatGrandChild1?.span_id);
49+
50+
expect(spans.every(s => s.name === 'parent-span' || !s.is_segment)).toBe(true);
51+
52+
expect(clientReport.discarded_events).toEqual([
53+
{
54+
category: 'span',
55+
quantity: 5, // 5 ignored child spans
56+
reason: 'ignored',
57+
},
58+
]);
59+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [Sentry.spanStreamingIntegration()],
8+
ignoreSpans: [/ignore/],
9+
tracesSampleRate: 1,
10+
debug: true,
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// This segment span matches ignoreSpans — should NOT produce a transaction
2+
Sentry.startSpan({ name: 'ignore-segment' }, () => {
3+
Sentry.startSpan({ name: 'child-of-ignored-segment' }, () => {});
4+
});
5+
6+
setTimeout(() => {
7+
// This segment span does NOT match — should produce a transaction
8+
Sentry.startSpan({ name: 'normal-segment' }, () => {
9+
Sentry.startSpan({ name: 'child-span' }, () => {});
10+
});
11+
}, 1000);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../../utils/fixtures';
3+
import {
4+
envelopeRequestParser,
5+
hidePage,
6+
shouldSkipTracingTest,
7+
testingCdnBundle,
8+
waitForClientReportRequest,
9+
} from '../../../../utils/helpers';
10+
import { observeStreamedSpan, waitForStreamedSpans } from '../../../../utils/spanUtils';
11+
import type { ClientReport } from '@sentry/core';
12+
13+
sentryTest('ignored segment span drops entire trace', async ({ getLocalTestUrl, page }) => {
14+
sentryTest.skip(shouldSkipTracingTest() || testingCdnBundle());
15+
16+
const url = await getLocalTestUrl({ testDir: __dirname });
17+
18+
observeStreamedSpan(page, span => {
19+
if (span.name === 'ignore-segment' || span.name === 'child-of-ignored-segment') {
20+
throw new Error('Ignored span found');
21+
}
22+
return false; // means we keep on looking for unwanted spans
23+
});
24+
25+
const spansPromise = waitForStreamedSpans(page, spans => !!spans?.find(s => s.name === 'normal-segment'));
26+
27+
const clientReportPromise = waitForClientReportRequest(page);
28+
29+
await page.goto(url);
30+
31+
expect((await spansPromise)?.length).toBe(2);
32+
33+
await hidePage(page);
34+
35+
const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);
36+
37+
expect(clientReport.discarded_events).toEqual([
38+
{
39+
category: 'span',
40+
quantity: 2, // segment + child span
41+
reason: 'ignored',
42+
},
43+
]);
44+
});

0 commit comments

Comments
 (0)