Skip to content

Commit e1dae9d

Browse files
committed
fix(nuxt): Flush serverless to send events
1 parent 121ae07 commit e1dae9d

File tree

1 file changed

+65
-1
lines changed

1 file changed

+65
-1
lines changed

packages/nuxt/src/runtime/plugins/sentry.server.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
1+
import {
2+
GLOBAL_OBJ,
3+
consoleSandbox,
4+
flush,
5+
getClient,
6+
getDefaultIsolationScope,
7+
getIsolationScope,
8+
logger,
9+
vercelWaitUntil,
10+
withIsolationScope,
11+
} from '@sentry/core';
112
import * as Sentry from '@sentry/node';
213
import { H3Error } from 'h3';
314
import { defineNitroPlugin } from 'nitropack/runtime';
415
import type { NuxtRenderHTMLContext } from 'nuxt/app';
516
import { addSentryTracingMetaTags, extractErrorContext } from '../utils';
617

718
export default defineNitroPlugin(nitroApp => {
8-
nitroApp.hooks.hook('error', (error, errorContext) => {
19+
nitroApp.h3App.handler = new Proxy(nitroApp.h3App.handler, {
20+
async apply(handlerTarget, handlerThisArg, handlerArgs: Parameters<typeof nitroApp.h3App.handler>) {
21+
// In environments where we cannot make use of OTel httpInstrumentation, e.g. when using top level import
22+
// of the server instrumentation file instead of `--import` or dynamic import like on vercel
23+
// we still need to ensure requests are properly isolated
24+
const isolationScope = getIsolationScope();
25+
const newIsolationScope = isolationScope === getDefaultIsolationScope() ? isolationScope.clone() : isolationScope;
26+
27+
consoleSandbox(() => {
28+
// eslint-disable-next-line no-console
29+
console.log(
30+
`[Sentry] Patched event handler. Using ${
31+
isolationScope === newIsolationScope ? 'existing' : 'new'
32+
} isolationscope.`,
33+
);
34+
});
35+
36+
return withIsolationScope(newIsolationScope, async () => {
37+
try {
38+
return await handlerTarget.apply(handlerThisArg, handlerArgs);
39+
} finally {
40+
await flushIfServerless();
41+
}
42+
});
43+
},
44+
});
45+
46+
nitroApp.hooks.hook('error', async (error, errorContext) => {
947
// Do not handle 404 and 422
1048
if (error instanceof H3Error) {
1149
// Do not report if status code is 3xx or 4xx
@@ -29,10 +67,36 @@ export default defineNitroPlugin(nitroApp => {
2967
captureContext: { contexts: { nuxt: structuredContext } },
3068
mechanism: { handled: false },
3169
});
70+
71+
await flushIfServerless();
3272
});
3373

3474
// @ts-expect-error - 'render:html' is a valid hook name in the Nuxt context
3575
nitroApp.hooks.hook('render:html', (html: NuxtRenderHTMLContext) => {
3676
addSentryTracingMetaTags(html.head);
3777
});
3878
});
79+
80+
async function flushIfServerless(): Promise<void> {
81+
const isServerless = !!process.env.LAMBDA_TASK_ROOT || !!process.env.VERCEL || !!process.env.NETLIFY;
82+
83+
// @ts-expect-error This is not typed
84+
if (GLOBAL_OBJ[Symbol.for('@vercel/request-context')]) {
85+
vercelWaitUntil(flushWithTimeout());
86+
} else if (isServerless) {
87+
await flushWithTimeout();
88+
}
89+
}
90+
91+
async function flushWithTimeout(): Promise<void> {
92+
const sentryClient = getClient();
93+
const isDebug = sentryClient ? sentryClient.getOptions().debug : false;
94+
95+
try {
96+
isDebug && logger.log('Flushing events...');
97+
await flush(2000);
98+
isDebug && logger.log('Done flushing events');
99+
} catch (e) {
100+
isDebug && logger.log('Error while flushing events:\n', e);
101+
}
102+
}

0 commit comments

Comments
 (0)