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' ;
1
12
import * as Sentry from '@sentry/node' ;
2
13
import { H3Error } from 'h3' ;
3
14
import { defineNitroPlugin } from 'nitropack/runtime' ;
4
15
import type { NuxtRenderHTMLContext } from 'nuxt/app' ;
5
16
import { addSentryTracingMetaTags , extractErrorContext } from '../utils' ;
6
17
7
18
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 ) => {
9
47
// Do not handle 404 and 422
10
48
if ( error instanceof H3Error ) {
11
49
// Do not report if status code is 3xx or 4xx
@@ -29,10 +67,36 @@ export default defineNitroPlugin(nitroApp => {
29
67
captureContext : { contexts : { nuxt : structuredContext } } ,
30
68
mechanism : { handled : false } ,
31
69
} ) ;
70
+
71
+ await flushIfServerless ( ) ;
32
72
} ) ;
33
73
34
74
// @ts -expect-error - 'render:html' is a valid hook name in the Nuxt context
35
75
nitroApp . hooks . hook ( 'render:html' , ( html : NuxtRenderHTMLContext ) => {
36
76
addSentryTracingMetaTags ( html . head ) ;
37
77
} ) ;
38
78
} ) ;
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