Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '116 KB',
limit: '117 KB',
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 4,
category: 'span',
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a couple of pre-existing tests (like this one) needed adjustments because they previously reported the wrong telemetry type (for span streaming). Also the number of dropped spans increased because we now also count child spans.

quantity: expect.any(Number),
reason: 'sample_rate',
},
],
});
// exact number depends on performance observer emissions
expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(10);
});

expect(spansReceived).toHaveLength(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
quantity: 2,
category: 'span',
quantity: expect.any(Number),
reason: 'sample_rate',
},
],
});
// exact number depends on performance observer emissions
expect(clientReport.discarded_events[0].quantity).toBeGreaterThanOrEqual(3);
});

await sentryTest.step('Navigate to another page with meta tags', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
category: 'span',
quantity: 1,
reason: 'sample_rate',
},
Expand All @@ -76,7 +76,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => {
timestamp: expect.any(Number),
discarded_events: [
{
category: 'transaction',
category: 'span',
quantity: 1,
reason: 'sample_rate',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.spanStreamingIntegration()],
ignoreSpans: [/ignore/],
parentSpanIsAlwaysRootSpan: false,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth noting: For this test, I enabled parentSpanIsAlwaysRootSpan which due to the lack of async context is our default behaviour in browser. I enabled it here to ensure that the parenting order in the test (see file below) works as expected in core/browser.

tracesSampleRate: 1,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Sentry.startSpan({ name: 'parent-span' }, () => {
Sentry.startSpan({ name: 'keep-me' }, () => {});

// This child matches ignoreSpans —> dropped
Sentry.startSpan({ name: 'ignore-child' }, () => {
// dropped
Sentry.startSpan({ name: 'ignore-grandchild-1' }, () => {
// kept
Sentry.startSpan({ name: 'great-grandchild-1' }, () => {
// dropped
Sentry.startSpan({ name: 'ignore-great-great-grandchild-1' }, () => {
// kept
Sentry.startSpan({ name: 'great-great-great-grandchild-1' }, () => {});
});
});
});
// Grandchild is reparented to 'parent-span' —> kept
Sentry.startSpan({ name: 'grandchild-2' }, () => {});
});

// kept
Sentry.startSpan({ name: 'another-keeper' }, () => {});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../../utils/fixtures';
import {
envelopeRequestParser,
hidePage,
shouldSkipTracingTest,
testingCdnBundle,
waitForClientReportRequest,
} from '../../../../utils/helpers';
import { waitForStreamedSpans } from '../../../../utils/spanUtils';
import type { ClientReport } from '@sentry/core';

sentryTest('ignored child spans are dropped and their children are reparented', async ({ getLocalTestUrl, page }) => {
sentryTest.skip(shouldSkipTracingTest() || testingCdnBundle());

const spansPromise = waitForStreamedSpans(page, spans => !!spans?.find(s => s.name === 'parent-span'));

const clientReportPromise = waitForClientReportRequest(page);

const url = await getLocalTestUrl({ testDir: __dirname });
await page.goto(url);

const spans = await spansPromise;

await hidePage(page);

const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);

const segmentSpanId = spans.find(s => s.name === 'parent-span')?.span_id;

expect(spans.length).toBe(6);

expect(spans.some(s => s.name === 'keep-me')).toBe(true);
expect(spans.some(s => s.name === 'another-keeper')).toBe(true);

expect(spans.some(s => s.name?.includes('ignore'))).toBe(false);

const greatGrandChild1 = spans.find(s => s.name === 'great-grandchild-1');
const grandchild2 = spans.find(s => s.name === 'grandchild-2');
const greatGreatGreatGrandChild1 = spans.find(s => s.name === 'great-great-great-grandchild-1');

expect(greatGrandChild1).toBeDefined();
expect(grandchild2).toBeDefined();
expect(greatGreatGreatGrandChild1).toBeDefined();

expect(greatGrandChild1?.parent_span_id).toBe(segmentSpanId);
expect(grandchild2?.parent_span_id).toBe(segmentSpanId);
expect(greatGreatGreatGrandChild1?.parent_span_id).toBe(greatGrandChild1?.span_id);

expect(spans.every(s => s.name === 'parent-span' || !s.is_segment)).toBe(true);

expect(clientReport.discarded_events).toEqual([
{
category: 'span',
quantity: 3, // 3 ignored child spans
reason: 'ignored',
},
]);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.spanStreamingIntegration()],
ignoreSpans: [/ignore/],
tracesSampleRate: 1,
debug: true,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This segment span matches ignoreSpans — should NOT produce a transaction
Sentry.startSpan({ name: 'ignore-segment' }, () => {
Sentry.startSpan({ name: 'child-of-ignored-segment' }, () => {});
});

setTimeout(() => {
// This segment span does NOT match — should produce a transaction
Sentry.startSpan({ name: 'normal-segment' }, () => {
Sentry.startSpan({ name: 'child-span' }, () => {});
});
}, 1000);
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../../utils/fixtures';
import {
envelopeRequestParser,
hidePage,
shouldSkipTracingTest,
testingCdnBundle,
waitForClientReportRequest,
} from '../../../../utils/helpers';
import { observeStreamedSpan, waitForStreamedSpans } from '../../../../utils/spanUtils';
import type { ClientReport } from '@sentry/core';

sentryTest('ignored segment span drops entire trace', async ({ getLocalTestUrl, page }) => {
sentryTest.skip(shouldSkipTracingTest() || testingCdnBundle());

const url = await getLocalTestUrl({ testDir: __dirname });

observeStreamedSpan(page, span => {
if (span.name === 'ignore-segment' || span.name === 'child-of-ignored-segment') {
throw new Error('Ignored span found');
}
return false; // means we keep on looking for unwanted spans
});

const spansPromise = waitForStreamedSpans(page, spans => !!spans?.find(s => s.name === 'normal-segment'));

const clientReportPromise = waitForClientReportRequest(page);

await page.goto(url);

expect((await spansPromise)?.length).toBe(2);

await hidePage(page);

const clientReport = envelopeRequestParser<ClientReport>(await clientReportPromise);

expect(clientReport.discarded_events).toEqual([
{
category: 'span',
quantity: 2, // segment + child span
reason: 'ignored',
},
]);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
transport: loggingTransport,
traceLifecycle: 'stream',
ignoreSpans: ['middleware - expressInit', 'custom-to-drop'],
clientReportFlushInterval: 1_000,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import express from 'express';
import cors from 'cors';
import * as Sentry from '@sentry/node';
import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';

const app = express();

app.use(cors());

app.get('/test/express', (_req, res) => {
Sentry.startSpan(
{
name: 'custom-to-drop',
op: 'custom',
},
() => {
Sentry.startSpan(
{
name: 'custom',
op: 'custom',
},
() => {},
);
},
);
res.send({ response: 'response 1' });
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { afterAll, describe, expect } from 'vitest';
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../../utils/runner';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';

describe('filtering child spans with ignoreSpans (streaming)', () => {
afterAll(() => {
cleanupChildProcesses();
});

createEsmAndCjsTests(__dirname, 'server.mjs', 'instrument.mjs', (createRunner, test) => {
test('child spans are dropped and remaining spans correctly parented', async () => {
const runner = createRunner()
.unignore('client_report')
.expect({
client_report: {
discarded_events: [
{
category: 'span',
quantity: 2,
reason: 'ignored',
},
],
},
})
.expect({
span: container => {
// 5 spans: 1 root, 2 middleware, 1 request handler, 1 custom
// Would be 7 if we didn't ignore the 'middleware - expressInit' and 'custom-to-drop' spans
expect(container.items).toHaveLength(5);
const getSpan = (name: string, op: string) =>
container.items.find(
item => item.name === name && item.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === op,
);
const queryMiddlewareSpan = getSpan('query', 'middleware.express');
const corsMiddlewareSpan = getSpan('corsMiddleware', 'middleware.express');
const requestHandlerSpan = getSpan('/test/express', 'request_handler.express');
const httpServerSpan = getSpan('GET /test/express', 'http.server');
const customSpan = getSpan('custom', 'custom');

expect(queryMiddlewareSpan).toBeDefined();
expect(corsMiddlewareSpan).toBeDefined();
expect(requestHandlerSpan).toBeDefined();
expect(httpServerSpan).toBeDefined();
expect(customSpan).toBeDefined();

expect(customSpan?.parent_span_id).toBe(requestHandlerSpan?.span_id);
expect(requestHandlerSpan?.parent_span_id).toBe(httpServerSpan?.span_id);
expect(queryMiddlewareSpan?.parent_span_id).toBe(httpServerSpan?.span_id);
expect(corsMiddlewareSpan?.parent_span_id).toBe(httpServerSpan?.span_id);
expect(httpServerSpan?.parent_span_id).toBeUndefined();
},
})
.start();

runner.makeRequest('get', '/test/express');

await runner.completed();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
transport: loggingTransport,
traceLifecycle: 'stream',
ignoreSpans: [/\/health/],
clientReportFlushInterval: 1_000,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import express from 'express';
import cors from 'cors';
import * as Sentry from '@sentry/node';
import { startExpressServerAndSendPortToRunner } from '@sentry-internal/node-integration-tests';

const app = express();

app.use(cors());

app.get('/health', (_req, res) => {
res.send({ status: 'ok-health' });
});

app.get('/ok', (_req, res) => {
res.send({ status: 'ok' });
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Loading
Loading