Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9039128

Browse files
committedJan 8, 2020
process: allow monitoring uncaughtException
Installing an uncaughtException listener has a side effect that process is not aborted. This is quite bad for monitoring/logging tools which tend to be interested in errors but don't want to cause side effects like swallow an exception or change the output on console. There are some workarounds in the wild like monkey patching emit or rethrow in the exception if monitoring tool detects that it is the only listener but this is error prone and risky. This PR allows to install a listener to monitor uncaughtException without the side effect to consider the exception has handled. To avoid conflicts with other events it exports a symbol on process which owns this special meaning.
1 parent b9e6c1b commit 9039128

File tree

4 files changed

+77
-2
lines changed

4 files changed

+77
-2
lines changed
 

‎doc/api/process.md

+28
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,20 @@ nonexistentFunc();
262262
console.log('This will not run.');
263263
```
264264

265+
It is possible to monitor `'uncaughtException'` events without overriding the
266+
default behavior to exit the process by installing a listener using the symbol
267+
`uncaughtExceptionMonitor`.
268+
269+
```js
270+
process.on(process.uncaughtExceptionMonitor, (err, origin) => {
271+
MyMonitoringTool.logSync(err, origin);
272+
});
273+
274+
// Intentionally cause an exception, but don't catch it.
275+
nonexistentFunc();
276+
// Still crashes Node.js
277+
```
278+
265279
#### Warning: Using `'uncaughtException'` correctly
266280

267281
`'uncaughtException'` is a crude mechanism for exception handling
@@ -2320,6 +2334,20 @@ documentation for the [`'warning'` event][process_warning] and the
23202334
[`emitWarning()` method][process_emit_warning] for more information about this
23212335
flag's behavior.
23222336

2337+
## `process.uncaughtExceptionMonitor`
2338+
<!-- YAML
2339+
added: REPLACEME
2340+
-->
2341+
2342+
This symbol shall be used to install a listener for only monitoring
2343+
`'uncaughtException'` events. Listeners installed using this symbol are called
2344+
before the regular `'uncaughtException'` listeners and before a hook
2345+
installed via [`process.setUncaughtExceptionCaptureCallback()`][].
2346+
2347+
Installing a listener using this symbol does not change the behavior once an
2348+
`'uncaughtException'` event is emitted, therefore the process will still crash
2349+
if no regular `'uncaughtException'` listener is installed.
2350+
23232351
## `process.umask([mask])`
23242352
<!-- YAML
23252353
added: v0.1.19

‎lib/internal/bootstrap/node.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,8 @@ ObjectDefineProperty(process, 'features', {
206206
const {
207207
onGlobalUncaughtException,
208208
setUncaughtExceptionCaptureCallback,
209-
hasUncaughtExceptionCaptureCallback
209+
hasUncaughtExceptionCaptureCallback,
210+
kUncaughtExceptionMonitor
210211
} = require('internal/process/execution');
211212

212213
// For legacy reasons this is still called `_fatalException`, even
@@ -220,6 +221,12 @@ ObjectDefineProperty(process, 'features', {
220221
setUncaughtExceptionCaptureCallback;
221222
process.hasUncaughtExceptionCaptureCallback =
222223
hasUncaughtExceptionCaptureCallback;
224+
ObjectDefineProperty(process, 'uncaughtExceptionMonitor', {
225+
value: kUncaughtExceptionMonitor,
226+
writable: false,
227+
configurable: true,
228+
enumerable: true
229+
});
223230
}
224231

225232
const { emitWarning } = require('internal/process/warning');

‎lib/internal/process/execution.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const {
44
JSONStringify,
55
PromiseResolve,
6+
Symbol,
67
} = primordials;
78

89
const path = require('path');
@@ -27,6 +28,8 @@ const {
2728
// communication with JS.
2829
const { shouldAbortOnUncaughtToggle } = internalBinding('util');
2930

31+
const kUncaughtExceptionMonitor = Symbol('process.uncaughtExceptionMonitor');
32+
3033
function tryGetCwd() {
3134
try {
3235
return process.cwd();
@@ -159,6 +162,7 @@ function createOnGlobalUncaughtException() {
159162
}
160163

161164
const type = fromPromise ? 'unhandledRejection' : 'uncaughtException';
165+
process.emit(kUncaughtExceptionMonitor, er, type);
162166
if (exceptionHandlerState.captureFn !== null) {
163167
exceptionHandlerState.captureFn(er);
164168
} else if (!process.emit('uncaughtException', er, type)) {
@@ -214,5 +218,6 @@ module.exports = {
214218
evalScript,
215219
onGlobalUncaughtException: createOnGlobalUncaughtException(),
216220
setUncaughtExceptionCaptureCallback,
217-
hasUncaughtExceptionCaptureCallback
221+
hasUncaughtExceptionCaptureCallback,
222+
kUncaughtExceptionMonitor
218223
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
6+
const theErr = new Error('MyError');
7+
8+
process.on(
9+
process.uncaughtExceptionMonitor,
10+
common.mustCall(function onUncaughtExceptionMonitor(err, origin) {
11+
assert.strictEqual(err, theErr);
12+
assert.strictEqual(origin, 'uncaughtException');
13+
}, 2)
14+
);
15+
16+
process.on('uncaughtException', common.mustCall(
17+
function onUncaughtException(err, origin) {
18+
assert.strictEqual(origin, 'uncaughtException');
19+
assert.strictEqual(err, theErr);
20+
})
21+
);
22+
23+
process.nextTick(common.mustCall(
24+
function withExceptionCaptureCallback() {
25+
process.setUncaughtExceptionCaptureCallback(common.mustCall(
26+
function uncaughtExceptionCaptureCallback(err) {
27+
assert.strictEqual(err, theErr);
28+
})
29+
);
30+
31+
throw theErr;
32+
})
33+
);
34+
35+
throw theErr;

0 commit comments

Comments
 (0)
Please sign in to comment.