Skip to content

Commit e711833

Browse files
committed
skip tunnel requests
1 parent 76e038b commit e711833

3 files changed

Lines changed: 155 additions & 18 deletions

File tree

packages/nextjs/src/common/utils/dropMiddlewareTunnelRequests.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
99
};
1010

1111
/**
12-
* Drops spans for tunnel requests from middleware or fetch instrumentation.
13-
* This catches both:
14-
* 1. Requests to the local tunnel route (before rewrite)
15-
* 2. Requests to Sentry ingest (after rewrite)
12+
* Drops spans for tunnel requests from middleware, fetch instrumentation, or BaseServer.handleRequest.
13+
* This catches:
14+
* 1. Requests to the local tunnel route (before rewrite) via middleware or BaseServer.handleRequest
15+
* 2. Requests to Sentry ingest (after rewrite) via fetch spans
1616
*/
1717
export function dropMiddlewareTunnelRequests(span: Span, attrs: SpanAttributes | undefined): void {
1818
// When the user brings their own OTel setup (skipOpenTelemetrySetup: true), we should not
@@ -21,14 +21,15 @@ export function dropMiddlewareTunnelRequests(span: Span, attrs: SpanAttributes |
2121
return;
2222
}
2323

24-
// Only filter middleware spans or HTTP fetch spans
24+
// Only filter middleware spans, HTTP fetch spans, or BaseServer.handleRequest spans
2525
const isMiddleware = attrs?.[ATTR_NEXT_SPAN_TYPE] === 'Middleware.execute';
2626
// The fetch span could be originating from rewrites re-writing a tunnel request
2727
// So we want to filter it out
2828
const isFetchSpan = attrs?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.node_fetch';
29+
const isBaseServerHandleRequest = attrs?.[ATTR_NEXT_SPAN_TYPE] === 'BaseServer.handleRequest';
2930

30-
// If the span is not a middleware span or a fetch span, return
31-
if (!isMiddleware && !isFetchSpan) {
31+
// If the span is not a middleware span, fetch span, or BaseServer.handleRequest span, return
32+
if (!isMiddleware && !isFetchSpan && !isBaseServerHandleRequest) {
3233
return;
3334
}
3435

packages/nextjs/src/server/index.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/n
4848

4949
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
5050
_sentryRewriteFramesDistDir?: string;
51-
_sentryRewritesTunnelPath?: string;
5251
_sentryRelease?: string;
5352
};
5453

@@ -207,16 +206,6 @@ export function init(options: NodeOptions): NodeClient | undefined {
207206
return null;
208207
}
209208

210-
// Filter out transactions for requests to the tunnel route
211-
if (
212-
(globalWithInjectedValues._sentryRewritesTunnelPath &&
213-
event.transaction === `POST ${globalWithInjectedValues._sentryRewritesTunnelPath}`) ||
214-
(process.env._sentryRewritesTunnelPath &&
215-
event.transaction === `POST ${process.env._sentryRewritesTunnelPath}`)
216-
) {
217-
return null;
218-
}
219-
220209
// Filter out requests to resolve source maps for stack frames in dev mode
221210
if (event.transaction?.match(/\/__nextjs_original-stack-frame/)) {
222211
return null;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { dropMiddlewareTunnelRequests } from '../../src/common/utils/dropMiddlewareTunnelRequests';
3+
import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../../src/common/span-attributes-with-logic-attached';
4+
5+
const globalWithInjectedValues = global as typeof global & {
6+
_sentryRewritesTunnelPath?: string;
7+
};
8+
9+
vi.mock('@sentry/core', async requireActual => {
10+
return {
11+
...(await requireActual<any>()),
12+
getClient: () => ({
13+
getOptions: () => ({}),
14+
}),
15+
};
16+
});
17+
18+
vi.mock('@sentry/opentelemetry', () => ({
19+
isSentryRequestSpan: () => false,
20+
}));
21+
22+
function createMockSpan(): { setAttribute: ReturnType<typeof vi.fn>; attributes: Record<string, unknown> } {
23+
const attributes: Record<string, unknown> = {};
24+
return {
25+
attributes,
26+
setAttribute: vi.fn((key: string, value: unknown) => {
27+
attributes[key] = value;
28+
}),
29+
};
30+
}
31+
32+
beforeEach(() => {
33+
globalWithInjectedValues._sentryRewritesTunnelPath = undefined;
34+
});
35+
36+
describe('dropMiddlewareTunnelRequests', () => {
37+
describe('BaseServer.handleRequest spans', () => {
38+
it('marks BaseServer.handleRequest span for dropping when http.target matches tunnel path', () => {
39+
globalWithInjectedValues._sentryRewritesTunnelPath = '/monitoring';
40+
const span = createMockSpan();
41+
42+
dropMiddlewareTunnelRequests(span as any, {
43+
'next.span_type': 'BaseServer.handleRequest',
44+
'http.target': '/monitoring?o=123&p=456',
45+
});
46+
47+
expect(span.setAttribute).toHaveBeenCalledWith(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true);
48+
});
49+
50+
it('marks BaseServer.handleRequest span for dropping when http.target exactly matches tunnel path', () => {
51+
globalWithInjectedValues._sentryRewritesTunnelPath = '/monitoring';
52+
const span = createMockSpan();
53+
54+
dropMiddlewareTunnelRequests(span as any, {
55+
'next.span_type': 'BaseServer.handleRequest',
56+
'http.target': '/monitoring',
57+
});
58+
59+
expect(span.setAttribute).toHaveBeenCalledWith(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true);
60+
});
61+
62+
it('does not mark BaseServer.handleRequest span for dropping when http.target does not match tunnel path', () => {
63+
globalWithInjectedValues._sentryRewritesTunnelPath = '/monitoring';
64+
const span = createMockSpan();
65+
66+
dropMiddlewareTunnelRequests(span as any, {
67+
'next.span_type': 'BaseServer.handleRequest',
68+
'http.target': '/api/users',
69+
});
70+
71+
expect(span.setAttribute).not.toHaveBeenCalled();
72+
});
73+
74+
it('does not mark BaseServer.handleRequest span when no tunnel path is configured', () => {
75+
const span = createMockSpan();
76+
77+
dropMiddlewareTunnelRequests(span as any, {
78+
'next.span_type': 'BaseServer.handleRequest',
79+
'http.target': '/monitoring',
80+
});
81+
82+
expect(span.setAttribute).not.toHaveBeenCalled();
83+
});
84+
85+
it('handles BaseServer.handleRequest span with basePath prefix in http.target', () => {
86+
globalWithInjectedValues._sentryRewritesTunnelPath = '/basepath/monitoring';
87+
const span = createMockSpan();
88+
89+
dropMiddlewareTunnelRequests(span as any, {
90+
'next.span_type': 'BaseServer.handleRequest',
91+
'http.target': '/basepath/monitoring?o=123&p=456',
92+
});
93+
94+
expect(span.setAttribute).toHaveBeenCalledWith(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true);
95+
});
96+
});
97+
98+
describe('Middleware.execute spans', () => {
99+
it('marks middleware span for dropping when http.target matches tunnel path', () => {
100+
globalWithInjectedValues._sentryRewritesTunnelPath = '/monitoring';
101+
const span = createMockSpan();
102+
103+
dropMiddlewareTunnelRequests(span as any, {
104+
'next.span_type': 'Middleware.execute',
105+
'http.target': '/monitoring?o=123&p=456',
106+
});
107+
108+
expect(span.setAttribute).toHaveBeenCalledWith(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true);
109+
});
110+
});
111+
112+
describe('unrelated spans', () => {
113+
it('does not process spans without matching span type or origin', () => {
114+
globalWithInjectedValues._sentryRewritesTunnelPath = '/monitoring';
115+
const span = createMockSpan();
116+
117+
dropMiddlewareTunnelRequests(span as any, {
118+
'next.span_type': 'SomeOtherSpanType',
119+
'http.target': '/monitoring',
120+
});
121+
122+
expect(span.setAttribute).not.toHaveBeenCalled();
123+
});
124+
});
125+
126+
describe('skipOpenTelemetrySetup', () => {
127+
it('does not process spans when skipOpenTelemetrySetup is true', async () => {
128+
const core = await import('@sentry/core');
129+
const originalGetClient = core.getClient;
130+
vi.spyOn(core, 'getClient').mockReturnValueOnce({
131+
getOptions: () => ({ skipOpenTelemetrySetup: true }),
132+
} as any);
133+
134+
globalWithInjectedValues._sentryRewritesTunnelPath = '/monitoring';
135+
const span = createMockSpan();
136+
137+
dropMiddlewareTunnelRequests(span as any, {
138+
'next.span_type': 'BaseServer.handleRequest',
139+
'http.target': '/monitoring',
140+
});
141+
142+
expect(span.setAttribute).not.toHaveBeenCalled();
143+
144+
vi.mocked(core.getClient).mockRestore();
145+
});
146+
});
147+
});

0 commit comments

Comments
 (0)