@@ -7,6 +7,28 @@ import { logAndExitProcess } from './utils/errorhandling';
7
7
8
8
type OnFatalErrorHandler = ( firstError : Error , secondError ?: Error ) => void ;
9
9
10
+ interface OnUncaughtExceptionOptions {
11
+ // TODO(v8): Evaluate whether we should switch the default behaviour here.
12
+ // Also, we can evaluate using https://nodejs.org/api/process.html#event-uncaughtexceptionmonitor per default, and
13
+ // falling back to current behaviour when that's not available.
14
+ /**
15
+ * Whether the SDK should mimic native behaviour when a global error occurs. Default: `false`
16
+ * - `false`: The SDK will exit the process on all uncaught errors.
17
+ * - `true`: The SDK will only exit the process when there are no other 'uncaughtException' handlers attached.
18
+ */
19
+ mimicNativeBehaviour : boolean ;
20
+
21
+ /**
22
+ * This is called when an uncaught error would cause the process to exit.
23
+ *
24
+ * @param firstError Uncaught error causing the process to exit
25
+ * @param secondError Will be set if the handler was called multiple times. This can happen either because
26
+ * `onFatalError` itself threw, or because an independent error happened somewhere else while `onFatalError`
27
+ * was running.
28
+ */
29
+ onFatalError ?( firstError : Error , secondError ?: Error ) : void ;
30
+ }
31
+
10
32
/** Global Exception handler */
11
33
export class OnUncaughtException implements Integration {
12
34
/**
@@ -24,19 +46,18 @@ export class OnUncaughtException implements Integration {
24
46
*/
25
47
public readonly handler : ( error : Error ) => void = this . _makeErrorHandler ( ) ;
26
48
49
+ private readonly _options : OnUncaughtExceptionOptions ;
50
+
27
51
/**
28
52
* @inheritDoc
29
53
*/
30
- public constructor (
31
- private readonly _options : {
32
- /**
33
- * Default onFatalError handler
34
- * @param firstError Error that has been thrown
35
- * @param secondError If this was called multiple times this will be set
36
- */
37
- onFatalError ?( firstError : Error , secondError ?: Error ) : void ;
38
- } = { } ,
39
- ) { }
54
+ public constructor ( options : Partial < OnUncaughtExceptionOptions > = { } ) {
55
+ this . _options = {
56
+ mimicNativeBehaviour : false ,
57
+ ...options ,
58
+ } ;
59
+ }
60
+
40
61
/**
41
62
* @inheritDoc
42
63
*/
@@ -58,6 +79,26 @@ export class OnUncaughtException implements Integration {
58
79
let onFatalError : OnFatalErrorHandler = logAndExitProcess ;
59
80
const client = getCurrentHub ( ) . getClient < NodeClient > ( ) ;
60
81
82
+ // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not
83
+ // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust
84
+ // exit behaviour of the SDK accordingly:
85
+ // - If other listeners are attached, do not exit.
86
+ // - If the only listener attached is ours, exit.
87
+ const userProvidedListenersCount = global . process
88
+ . listeners ( 'uncaughtException' )
89
+ . reduce < number > ( ( acc , listener ) => {
90
+ if (
91
+ listener . name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself
92
+ listener === this . handler // filter the handler we registered ourselves)
93
+ ) {
94
+ return acc ;
95
+ } else {
96
+ return acc + 1 ;
97
+ }
98
+ } , 0 ) ;
99
+
100
+ const shouldExitProcess : boolean = ! this . _options . mimicNativeBehaviour || userProvidedListenersCount === 0 ;
101
+
61
102
if ( this . _options . onFatalError ) {
62
103
// eslint-disable-next-line @typescript-eslint/unbound-method
63
104
onFatalError = this . _options . onFatalError ;
@@ -82,23 +123,23 @@ export class OnUncaughtException implements Integration {
82
123
originalException : error ,
83
124
data : { mechanism : { handled : false , type : 'onuncaughtexception' } } ,
84
125
} ) ;
85
- if ( ! calledFatalError ) {
126
+ if ( ! calledFatalError && shouldExitProcess ) {
86
127
calledFatalError = true ;
87
128
onFatalError ( error ) ;
88
129
}
89
130
} ) ;
90
131
} else {
91
- if ( ! calledFatalError ) {
132
+ if ( ! calledFatalError && shouldExitProcess ) {
92
133
calledFatalError = true ;
93
134
onFatalError ( error ) ;
94
135
}
95
136
}
96
- } else if ( calledFatalError ) {
137
+ } else if ( calledFatalError && shouldExitProcess ) {
97
138
// we hit an error *after* calling onFatalError - pretty boned at this point, just shut it down
98
139
__DEBUG_BUILD__ &&
99
140
logger . warn ( 'uncaught exception after calling fatal error shutdown callback - this is bad! forcing shutdown' ) ;
100
141
logAndExitProcess ( error ) ;
101
- } else if ( ! caughtSecondError ) {
142
+ } else if ( ! caughtSecondError && shouldExitProcess ) {
102
143
// two cases for how we can hit this branch:
103
144
// - capturing of first error blew up and we just caught the exception from that
104
145
// - quit trying to capture, proceed with shutdown
0 commit comments