Skip to content

Commit ec094db

Browse files
authored
feat(sveltekit): Add option to control handling of unknown server routes (#8201)
Adds a `handleUnknownRoutes` option to the `sentryHandle` server request handler. The option defaults to `false` which will effectively reduce noise, such as random bot requests. Previously such requests would yield e.g. `GET null` transactions because they could not be matched to an existing route by the framework. See #8199
1 parent 3bbc1a5 commit ec094db

File tree

2 files changed

+74
-14
lines changed

2 files changed

+74
-14
lines changed

packages/sveltekit/src/server/handle.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ import type { Handle, ResolveOptions } from '@sveltejs/kit';
88
import { isHttpError, isRedirect } from '../common/utils';
99
import { getTracePropagationData } from './utils';
1010

11+
export type SentryHandleOptions = {
12+
/**
13+
* Controls whether the SDK should capture errors and traces in requests that don't belong to a
14+
* route defined in your SvelteKit application.
15+
*
16+
* By default, this option is set to `false` to reduce noise (e.g. bots sending random requests to your server).
17+
*
18+
* Set this option to `true` if you want to monitor requests events without a route. This might be useful in certain
19+
* scenarios, for instance if you registered other handlers that handle these requests.
20+
* If you set this option, you might want adjust the the transaction name in the `beforeSendTransaction`
21+
* callback of your server-side `Sentry.init` options. You can also use `beforeSendTransaction` to filter out
22+
* transactions that you still don't want to be sent to Sentry.
23+
*
24+
* @default false
25+
*/
26+
handleUnknownRoutes?: boolean;
27+
};
28+
1129
function sendErrorToSentry(e: unknown): unknown {
1230
// In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can
1331
// store a seen flag on it.
@@ -68,33 +86,42 @@ export const transformPageChunk: NonNullable<ResolveOptions['transformPageChunk'
6886
* // export const handle = sequence(sentryHandle(), yourCustomHandler);
6987
* ```
7088
*/
71-
export function sentryHandle(): Handle {
89+
export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle {
90+
const options = {
91+
handleUnknownRoutes: false,
92+
...handlerOptions,
93+
};
94+
95+
const sentryRequestHandler: Handle = input => {
96+
// if there is an active transaction, we know that this handle call is nested and hence
97+
// we don't create a new domain for it. If we created one, nested server calls would
98+
// create new transactions instead of adding a child span to the currently active span.
99+
if (getCurrentHub().getScope().getSpan()) {
100+
return instrumentHandle(input, options);
101+
}
102+
return runWithAsyncContext(() => {
103+
return instrumentHandle(input, options);
104+
});
105+
};
106+
72107
return sentryRequestHandler;
73108
}
74109

75-
const sentryRequestHandler: Handle = input => {
76-
// if there is an active transaction, we know that this handle call is nested and hence
77-
// we don't create a new domain for it. If we created one, nested server calls would
78-
// create new transactions instead of adding a child span to the currently active span.
79-
if (getCurrentHub().getScope().getSpan()) {
80-
return instrumentHandle(input);
110+
function instrumentHandle({ event, resolve }: Parameters<Handle>[0], options: SentryHandleOptions): ReturnType<Handle> {
111+
if (!event.route?.id && !options.handleUnknownRoutes) {
112+
return resolve(event);
81113
}
82-
return runWithAsyncContext(() => {
83-
return instrumentHandle(input);
84-
});
85-
};
86114

87-
function instrumentHandle({ event, resolve }: Parameters<Handle>[0]): ReturnType<Handle> {
88115
const { traceparentData, dynamicSamplingContext } = getTracePropagationData(event);
89116

90117
return trace(
91118
{
92119
op: 'http.server',
93-
name: `${event.request.method} ${event.route.id}`,
120+
name: `${event.request.method} ${event.route?.id || event.url.pathname}`,
94121
status: 'ok',
95122
...traceparentData,
96123
metadata: {
97-
source: 'route',
124+
source: event.route?.id ? 'route' : 'url',
98125
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
99126
},
100127
},

packages/sveltekit/test/server/handle.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,39 @@ describe('handleSentry', () => {
333333
expect(mockResolve).toHaveBeenCalledTimes(1);
334334
expect(mockResolve).toHaveBeenCalledWith(event, { transformPageChunk: expect.any(Function) });
335335
});
336+
337+
it("doesn't create a transaction if there's no route", async () => {
338+
let ref: any = undefined;
339+
client.on('finishTransaction', (transaction: Transaction) => {
340+
ref = transaction;
341+
});
342+
343+
try {
344+
await sentryHandle()({ event: mockEvent({ route: undefined }), resolve: resolve(type, isError) });
345+
} catch {
346+
//
347+
}
348+
349+
expect(ref).toBeUndefined();
350+
});
351+
352+
it("Creates a transaction if there's no route but `handleUnknownRequests` is true", async () => {
353+
let ref: any = undefined;
354+
client.on('finishTransaction', (transaction: Transaction) => {
355+
ref = transaction;
356+
});
357+
358+
try {
359+
await sentryHandle({ handleUnknownRoutes: true })({
360+
event: mockEvent({ route: undefined }),
361+
resolve: resolve(type, isError),
362+
});
363+
} catch {
364+
//
365+
}
366+
367+
expect(ref).toBeDefined();
368+
});
336369
});
337370
});
338371

0 commit comments

Comments
 (0)