Skip to content
Draft
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
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',
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,
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,60 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../../utils/fixtures';
import {
envelopeRequestParser,
hidePage,
shouldSkipTracingTest,
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 }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

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,45 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../../utils/fixtures';
import {
envelopeRequestParser,
hidePage,
shouldSkipTracingTest,
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 }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

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