Skip to content

Commit 83be44f

Browse files
committed
avoid double request isolation, add tests
1 parent 4c2f4d7 commit 83be44f

File tree

4 files changed

+67
-34
lines changed

4 files changed

+67
-34
lines changed

packages/sveltekit/src/server-common/handle.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ export function addSentryCodeToPage(options: { injectFetchProxyScript: boolean }
7373
};
7474
}
7575

76+
/**
77+
* We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0.
78+
* Exported only for testing.
79+
*/
80+
export function isFetchProxyRequired(version: string): boolean {
81+
try {
82+
const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number);
83+
if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) {
84+
return false;
85+
}
86+
} catch {
87+
// ignore
88+
}
89+
return true;
90+
}
91+
7692
async function instrumentHandle(
7793
{ event, resolve }: Parameters<Handle>[0],
7894
options: SentryHandleOptions,
@@ -134,22 +150,6 @@ async function instrumentHandle(
134150
}
135151
}
136152

137-
/**
138-
* We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0.
139-
* Exported only for testing.
140-
*/
141-
export function isFetchProxyRequired(version: string): boolean {
142-
try {
143-
const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number);
144-
if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) {
145-
return false;
146-
}
147-
} catch {
148-
// ignore
149-
}
150-
return true;
151-
}
152-
153153
/**
154154
* A SvelteKit handle function that wraps the request for Sentry error and
155155
* performance monitoring.
@@ -166,10 +166,10 @@ export function isFetchProxyRequired(version: string): boolean {
166166
* ```
167167
*/
168168
export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle {
169+
const { handleUnknownRoutes, ...rest } = handlerOptions ?? {};
169170
const options = {
170-
handleUnknownRoutes: false,
171-
injectFetchProxyScript: true,
172-
...handlerOptions,
171+
handleUnknownRoutes: handleUnknownRoutes ?? false,
172+
...rest,
173173
};
174174

175175
const sentryRequestHandler: Handle = input => {
@@ -185,7 +185,11 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle {
185185
// we create a new execution context.
186186
const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan();
187187

188-
if (isSubRequest) {
188+
// Escape hatch to suppress request isolation and trace continuation (see initCloudflareSentryHandle)
189+
const skipIsolation =
190+
'_sentrySkipRequestIsolation' in input.event.locals && input.event.locals._sentrySkipRequestIsolation;
191+
192+
if (isSubRequest || skipIsolation) {
189193
return instrumentHandle(input, options);
190194
}
191195

packages/sveltekit/src/server/handle.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ export function isFetchProxyRequired(version: string): boolean {
149149
return true;
150150
}
151151

152-
/** Documented in `worker/handle.ts` */
152+
/**
153+
* actual implementation in ../worker/handle.ts
154+
* @return no-op handler when initCLoudflareSentryHandle is called via node/server entry point
155+
*/
153156
export function initCloudflareSentryHandle(_options: unknown): Handle {
154157
return ({ event, resolve }) => resolve(event);
155158
}

packages/sveltekit/src/worker/handle.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sen
33
import type { Handle } from '@sveltejs/kit';
44

55
import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration';
6+
import { addNonEnumerableProperty } from '@sentry/core';
67

78
/** Initializes Sentry SvelteKit Cloudflare SDK
89
* This should be before the sentryHandle() call.
910
*
10-
* In Node.js, this is a stub that does nothing.
11-
* */
11+
* In the Node export, this is a stub that does nothing.
12+
*/
1213
export function initCloudflareSentryHandle(options: CloudflareOptions): Handle {
1314
const opts: CloudflareOptions = {
1415
defaultIntegrations: [...getDefaultCloudflareIntegrations(options), rewriteFramesIntegration()],
@@ -17,17 +18,24 @@ export function initCloudflareSentryHandle(options: CloudflareOptions): Handle {
1718

1819
const handleInitSentry: Handle = ({ event, resolve }) => {
1920
// if event.platform exists (should be there in a cloudflare worker), then do the cloudflare sentry init
20-
return event.platform
21-
? wrapRequestHandler(
22-
{
23-
options: opts,
24-
request: event.request,
25-
// @ts-expect-error This will exist in Cloudflare
26-
context: event.platform.context,
27-
},
28-
() => resolve(event),
29-
)
30-
: resolve(event);
21+
if (event.platform) {
22+
// This is an optional local that the `sentryHandle` handler checks for to avoid double isolation
23+
// In Cloudflare the `wrapRequestHandler` function already takes care of
24+
// - request isolation
25+
// - trace continuation
26+
// -setting the request onto the scope
27+
addNonEnumerableProperty(event.locals, '_sentrySkipRequestIsolation', true);
28+
return wrapRequestHandler(
29+
{
30+
options: opts,
31+
request: event.request,
32+
// @ts-expect-error This will exist in Cloudflare
33+
context: event.platform.context,
34+
},
35+
() => resolve(event),
36+
);
37+
}
38+
return resolve(event);
3139
};
3240

3341
return handleInitSentry;

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ beforeEach(() => {
9999
client.init();
100100

101101
mockCaptureException.mockClear();
102+
vi.clearAllMocks();
102103
});
103104

104105
describe('sentryHandle', () => {
@@ -367,6 +368,23 @@ describe('sentryHandle', () => {
367368

368369
expect(_span!).toBeDefined();
369370
});
371+
372+
it("doesn't create an isolation scope when the `_sentrySkipRequestIsolation` local is set", async () => {
373+
const withIsolationScopeSpy = vi.spyOn(SentryCore, 'withIsolationScope');
374+
const continueTraceSpy = vi.spyOn(SentryCore, 'continueTrace');
375+
376+
try {
377+
await sentryHandle({ handleUnknownRoutes: true })({
378+
event: { ...mockEvent({ route: undefined }), locals: { _sentrySkipRequestIsolation: true } },
379+
resolve: resolve(type, isError),
380+
});
381+
} catch {
382+
//
383+
}
384+
385+
expect(withIsolationScopeSpy).not.toHaveBeenCalled();
386+
expect(continueTraceSpy).not.toHaveBeenCalled();
387+
});
370388
});
371389
});
372390

0 commit comments

Comments
 (0)