Skip to content

Commit 0d01d51

Browse files
committed
avoid double request isolation, add tests
1 parent 77c90f7 commit 0d01d51

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { Handle } from '@sveltejs/kit';
22

3-
/** Documented in `worker/handle.ts` */
3+
/**
4+
* actual implementation in ../worker/handle.ts
5+
* @return no-op handler when initCLoudflareSentryHandle is called via node/server entry point
6+
*/
47
export function initCloudflareSentryHandle(_options: unknown): Handle {
58
return ({ event, resolve }) => resolve(event);
69
}

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
@@ -101,6 +101,7 @@ beforeEach(() => {
101101
client.init();
102102

103103
mockCaptureException.mockClear();
104+
vi.clearAllMocks();
104105
});
105106

106107
describe('sentryHandle', () => {
@@ -416,6 +417,23 @@ describe('sentryHandle', () => {
416417

417418
expect(_span!).toBeDefined();
418419
});
420+
421+
it("doesn't create an isolation scope when the `_sentrySkipRequestIsolation` local is set", async () => {
422+
const withIsolationScopeSpy = vi.spyOn(SentryCore, 'withIsolationScope');
423+
const continueTraceSpy = vi.spyOn(SentryCore, 'continueTrace');
424+
425+
try {
426+
await sentryHandle({ handleUnknownRoutes: true })({
427+
event: { ...mockEvent({ route: undefined }), locals: { _sentrySkipRequestIsolation: true } },
428+
resolve: resolve(type, isError),
429+
});
430+
} catch {
431+
//
432+
}
433+
434+
expect(withIsolationScopeSpy).not.toHaveBeenCalled();
435+
expect(continueTraceSpy).not.toHaveBeenCalled();
436+
});
419437
});
420438
});
421439

0 commit comments

Comments
 (0)