From 30b30f2d7f3af03a16d8361bbc01acff37dbd471 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Fri, 6 Dec 2024 14:12:27 +0100 Subject: [PATCH 1/2] feat(nitro-utils): Add `patchEventHandler` helper for nitro --- packages/nitro-utils/src/index.ts | 2 + .../src/nitro/patchEventHandler.ts | 39 +++++++++++++++++++ packages/nitro-utils/src/util/flush.ts | 30 ++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 packages/nitro-utils/src/nitro/patchEventHandler.ts create mode 100644 packages/nitro-utils/src/util/flush.ts diff --git a/packages/nitro-utils/src/index.ts b/packages/nitro-utils/src/index.ts index 9aa98faf5d62..92a212e16f1d 100644 --- a/packages/nitro-utils/src/index.ts +++ b/packages/nitro-utils/src/index.ts @@ -1,3 +1,5 @@ +export { patchEventHandler } from './nitro/patchEventHandler'; + export { wrapServerEntryWithDynamicImport, type WrapServerEntryPluginOptions, diff --git a/packages/nitro-utils/src/nitro/patchEventHandler.ts b/packages/nitro-utils/src/nitro/patchEventHandler.ts new file mode 100644 index 000000000000..8ae8242907f3 --- /dev/null +++ b/packages/nitro-utils/src/nitro/patchEventHandler.ts @@ -0,0 +1,39 @@ +import { getDefaultIsolationScope, getIsolationScope, logger, withIsolationScope } from '@sentry/core'; +import type { EventHandler } from 'h3'; +import { flushIfServerless } from '../util/flush'; + +/** + * A helper to patch a given `h3` event handler, ensuring that + * requests are properly isolated and data is flushed to Sentry. + */ +export function patchEventHandler(handler: EventHandler): EventHandler { + return new Proxy(handler, { + async apply(handlerTarget, handlerThisArg, handlerArgs: Parameters) { + // In environments where we cannot make use of the OTel + // http instrumentation (e.g. when using top level import + // of the server instrumentation file instead of + // `--import` or dynamic import, like on vercel) + // we still need to ensure requests are properly isolated + // by comparing the current isolation scope to the default + // one. + // Requests are properly isolated if they differ. + // If that's not the case, we fork the isolation scope here. + const isolationScope = getIsolationScope(); + const newIsolationScope = isolationScope === getDefaultIsolationScope() ? isolationScope.clone() : isolationScope; + + logger.log( + `[Sentry] Patched h3 event handler. ${ + isolationScope === newIsolationScope ? 'Using existing' : 'Created new' + } isolation scope.`, + ); + + return withIsolationScope(newIsolationScope, async () => { + try { + return await handlerTarget.apply(handlerThisArg, handlerArgs); + } finally { + await flushIfServerless(); + } + }); + }, + }); +} diff --git a/packages/nitro-utils/src/util/flush.ts b/packages/nitro-utils/src/util/flush.ts new file mode 100644 index 000000000000..8e8f08cf1a3b --- /dev/null +++ b/packages/nitro-utils/src/util/flush.ts @@ -0,0 +1,30 @@ +import { GLOBAL_OBJ, flush, getClient, logger, vercelWaitUntil } from '@sentry/core'; + +/** + * Flushes Sentry for serverless environments. + */ +export async function flushIfServerless(): Promise { + const isServerless = !!process.env.LAMBDA_TASK_ROOT || !!process.env.VERCEL || !!process.env.NETLIFY; + + // @ts-expect-error - this is not typed + if (GLOBAL_OBJ[Symbol.for('@vercel/request-context')]) { + vercelWaitUntil(flushWithTimeout()); + } else if (isServerless) { + await flushWithTimeout(); + } +} + +/** + * Flushes Sentry. + */ +export async function flushWithTimeout(): Promise { + const isDebug = getClient()?.getOptions()?.debug; + + try { + isDebug && logger.log('Flushing events...'); + await flush(2000); + isDebug && logger.log('Done flushing events'); + } catch (e) { + isDebug && logger.log('Error while flushing events:\n', e); + } +} From 1bc01a57752e9677866d6a5c337a9fc9ac9e171a Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:20:39 +0100 Subject: [PATCH 2/2] Update packages/nitro-utils/src/nitro/patchEventHandler.ts Co-authored-by: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> --- packages/nitro-utils/src/nitro/patchEventHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nitro-utils/src/nitro/patchEventHandler.ts b/packages/nitro-utils/src/nitro/patchEventHandler.ts index 8ae8242907f3..d15da9f0db27 100644 --- a/packages/nitro-utils/src/nitro/patchEventHandler.ts +++ b/packages/nitro-utils/src/nitro/patchEventHandler.ts @@ -22,7 +22,7 @@ export function patchEventHandler(handler: EventHandler): EventHandler { const newIsolationScope = isolationScope === getDefaultIsolationScope() ? isolationScope.clone() : isolationScope; logger.log( - `[Sentry] Patched h3 event handler. ${ + `Patched h3 event handler. ${ isolationScope === newIsolationScope ? 'Using existing' : 'Created new' } isolation scope.`, );