Skip to content

Commit b045541

Browse files
Lms24nicohrubec
andauthored
feat(nextjs): Filter unwanted segments when span streaming is enabled (#20384)
closes #20374 In this case we can get rid of the event processors entirely and just rely on `ignoreSpans` for transactions and streamed spans. Updated unit tests to include the span streaming path. We should get E2E coverage for this from our existing E2E tests. --------- Co-authored-by: Nicolas Hrubec <nico.hrubec@sentry.io>
1 parent a501f75 commit b045541

2 files changed

Lines changed: 70 additions & 23 deletions

File tree

packages/nextjs/src/client/index.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,17 @@ export function init(options: BrowserOptions): Client | undefined {
7474
applyTunnelRouteOption(opts);
7575
applySdkMetadata(opts, 'nextjs', ['nextjs', 'react']);
7676

77-
const client = reactInit(opts);
78-
79-
const filterTransactions: EventProcessor = event =>
80-
event.type === 'transaction' && event.transaction === '/404' ? null : event;
81-
filterTransactions.id = 'NextClient404Filter';
82-
addEventProcessor(filterTransactions);
77+
opts.ignoreSpans = [
78+
...(opts.ignoreSpans || []),
79+
// we filter out segment spans for /404 pages
80+
/^\/404$/,
81+
// segment spans where we didn't get a reasonable transaction name
82+
// in this case, constructing a dynamic RegExp is fine because the variable is a constant
83+
// we need to ensure to exact-match, so a string match isn't safe (same for /404 above)
84+
new RegExp(`^${INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME}$`),
85+
];
8386

84-
const filterIncompleteNavigationTransactions: EventProcessor = event =>
85-
event.type === 'transaction' && event.transaction === INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME
86-
? null
87-
: event;
88-
filterIncompleteNavigationTransactions.id = 'IncompleteTransactionFilter';
89-
addEventProcessor(filterIncompleteNavigationTransactions);
87+
const client = reactInit(opts);
9088

9189
const filterNextRedirectError: EventProcessor = (event, hint) =>
9290
isRedirectNavigationError(hint?.originalException) || event.exception?.values?.[0]?.value === 'NEXT_REDIRECT'

packages/nextjs/test/clientSdk.test.ts

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { Integration } from '@sentry/core';
2-
import { debug, getGlobalScope, getIsolationScope } from '@sentry/core';
2+
import { debug, getGlobalScope, getIsolationScope, SentryNonRecordingSpan } from '@sentry/core';
33
import * as SentryReact from '@sentry/react';
44
import { getClient, getCurrentScope, WINDOW } from '@sentry/react';
55
import { JSDOM } from 'jsdom';
66
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
77
import { breadcrumbsIntegration, browserTracingIntegration, init } from '../src/client';
8+
import { INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME } from '../src/client/routing/appRouterRoutingInstrumentation';
89

910
const reactInit = vi.spyOn(SentryReact, 'init');
1011
const debugLogSpy = vi.spyOn(debug, 'log');
@@ -83,20 +84,68 @@ describe('Client init()', () => {
8384
);
8485
});
8586

86-
it('adds 404 transaction filter', () => {
87-
init({
88-
dsn: 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012',
89-
tracesSampleRate: 1.0,
87+
describe('transaction filtering', () => {
88+
const TEST_DSN_404 = 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012';
89+
90+
it('drops /404 transactions', () => {
91+
init({ dsn: TEST_DSN_404, tracesSampleRate: 1.0 });
92+
const transportSend = vi.spyOn(getClient()!.getTransport()!, 'send');
93+
94+
// Ensure we have no current span, so our next span is a transaction
95+
SentryReact.withActiveSpan(null, () => {
96+
SentryReact.startInactiveSpan({ name: '/404' })?.end();
97+
});
98+
99+
expect(transportSend).not.toHaveBeenCalled();
100+
expect(debugLogSpy).toHaveBeenCalledWith(expect.stringContaining('matches `ignoreSpans`'));
90101
});
91-
const transportSend = vi.spyOn(getClient()!.getTransport()!, 'send');
92102

93-
// Ensure we have no current span, so our next span is a transaction
94-
SentryReact.withActiveSpan(null, () => {
95-
SentryReact.startInactiveSpan({ name: '/404' })?.end();
103+
it('drops incomplete navigation transactions', () => {
104+
init({ dsn: TEST_DSN_404, tracesSampleRate: 1.0 });
105+
const transportSend = vi.spyOn(getClient()!.getTransport()!, 'send');
106+
107+
// Ensure we have no current span, so our next span is a transaction
108+
SentryReact.withActiveSpan(null, () => {
109+
SentryReact.startInactiveSpan({ name: INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME })?.end();
110+
});
111+
112+
expect(transportSend).not.toHaveBeenCalled();
113+
expect(debugLogSpy).toHaveBeenCalledWith(expect.stringContaining('matches `ignoreSpans`'));
96114
});
97115

98-
expect(transportSend).not.toHaveBeenCalled();
99-
expect(debugLogSpy).toHaveBeenCalledWith('An event processor returned `null`, will not send event.');
116+
describe('span streaming', () => {
117+
it('drops /404 segment spans', () => {
118+
init({ dsn: TEST_DSN_404, tracesSampleRate: 1.0, traceLifecycle: 'stream' });
119+
120+
// Ensure we have no current span, so our next span is a segment span
121+
const span = SentryReact.withActiveSpan(null, () => SentryReact.startInactiveSpan({ name: '/404' }));
122+
123+
expect(span).toBeInstanceOf(SentryNonRecordingSpan);
124+
expect(debugLogSpy).toHaveBeenCalledWith(expect.stringContaining('matches `ignoreSpans`'));
125+
});
126+
127+
it('drops incomplete navigation segment spans', () => {
128+
init({ dsn: TEST_DSN_404, tracesSampleRate: 1.0, traceLifecycle: 'stream' });
129+
130+
// Ensure we have no current span, so our next span is a segment span
131+
const span = SentryReact.withActiveSpan(null, () =>
132+
SentryReact.startInactiveSpan({ name: INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME }),
133+
);
134+
135+
expect(span).toBeInstanceOf(SentryNonRecordingSpan);
136+
expect(debugLogSpy).toHaveBeenCalledWith(expect.stringContaining('matches `ignoreSpans`'));
137+
});
138+
139+
it('drops /404 non-segment spans', () => {
140+
init({ dsn: TEST_DSN_404, tracesSampleRate: 1.0, traceLifecycle: 'stream' });
141+
142+
SentryReact.startSpan({ name: 'parent' }, parent => {
143+
expect(parent).not.toBeInstanceOf(SentryNonRecordingSpan);
144+
const child = SentryReact.startInactiveSpan({ name: '/404' });
145+
expect(child).toBeInstanceOf(SentryNonRecordingSpan);
146+
});
147+
});
148+
});
100149
});
101150

102151
describe('integrations', () => {

0 commit comments

Comments
 (0)