Skip to content

Commit 0e7c0ad

Browse files
andreiborzas1gr1d
andcommitted
feat(nitro-utils): Add patchEventHandler helper for nitro (#14614)
E2E tests and usage in nuxt in: #14612 --------- Co-authored-by: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com>
1 parent 05e9dff commit 0e7c0ad

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

packages/nitro-utils/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export { patchEventHandler } from './nitro/patchEventHandler';
2+
13
export {
24
wrapServerEntryWithDynamicImport,
35
type WrapServerEntryPluginOptions,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { getDefaultIsolationScope, getIsolationScope, logger, withIsolationScope } from '@sentry/core';
2+
import type { EventHandler } from 'h3';
3+
import { flushIfServerless } from '../util/flush';
4+
5+
/**
6+
* A helper to patch a given `h3` event handler, ensuring that
7+
* requests are properly isolated and data is flushed to Sentry.
8+
*/
9+
export function patchEventHandler(handler: EventHandler): EventHandler {
10+
return new Proxy(handler, {
11+
async apply(handlerTarget, handlerThisArg, handlerArgs: Parameters<EventHandler>) {
12+
// In environments where we cannot make use of the OTel
13+
// http instrumentation (e.g. when using top level import
14+
// of the server instrumentation file instead of
15+
// `--import` or dynamic import, like on vercel)
16+
// we still need to ensure requests are properly isolated
17+
// by comparing the current isolation scope to the default
18+
// one.
19+
// Requests are properly isolated if they differ.
20+
// If that's not the case, we fork the isolation scope here.
21+
const isolationScope = getIsolationScope();
22+
const newIsolationScope = isolationScope === getDefaultIsolationScope() ? isolationScope.clone() : isolationScope;
23+
24+
logger.log(
25+
`Patched h3 event handler. ${
26+
isolationScope === newIsolationScope ? 'Using existing' : 'Created new'
27+
} isolation scope.`,
28+
);
29+
30+
return withIsolationScope(newIsolationScope, async () => {
31+
try {
32+
return await handlerTarget.apply(handlerThisArg, handlerArgs);
33+
} finally {
34+
await flushIfServerless();
35+
}
36+
});
37+
},
38+
});
39+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { GLOBAL_OBJ, flush, getClient, logger, vercelWaitUntil } from '@sentry/core';
2+
3+
/**
4+
* Flushes Sentry for serverless environments.
5+
*/
6+
export async function flushIfServerless(): Promise<void> {
7+
const isServerless = !!process.env.LAMBDA_TASK_ROOT || !!process.env.VERCEL || !!process.env.NETLIFY;
8+
9+
// @ts-expect-error - this is not typed
10+
if (GLOBAL_OBJ[Symbol.for('@vercel/request-context')]) {
11+
vercelWaitUntil(flushWithTimeout());
12+
} else if (isServerless) {
13+
await flushWithTimeout();
14+
}
15+
}
16+
17+
/**
18+
* Flushes Sentry.
19+
*/
20+
export async function flushWithTimeout(): Promise<void> {
21+
const isDebug = getClient()?.getOptions()?.debug;
22+
23+
try {
24+
isDebug && logger.log('Flushing events...');
25+
await flush(2000);
26+
isDebug && logger.log('Done flushing events');
27+
} catch (e) {
28+
isDebug && logger.log('Error while flushing events:\n', e);
29+
}
30+
}

0 commit comments

Comments
 (0)