GeezerVison is showing github.com — process: allow monitoring uncaughtException · nodejs/node@79eba6a
File treeExpand file treeCollapse file tree Open diff view settings fixtures/uncaught-exceptions
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/process.md +32Lines changed: 32 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -262,6 +262,10 @@ nonexistentFunc(); | 262 | 262 | console.log('This will not run.'); | 263 | 263 | ``` | 264 | 264 |
| | 265 | +It is possible to monitor `'uncaughtException'` events without overriding the | | 266 | +default behavior to exit the process by installing a | | 267 | +`'uncaughtExceptionMonitor'` listener. | | 268 | + | 265 | 269 | #### Warning: Using `'uncaughtException'` correctly | 266 | 270 |
| 267 | 271 | `'uncaughtException'` is a crude mechanism for exception handling | @@ -289,6 +293,34 @@ To restart a crashed application in a more reliable way, whether | 289 | 293 | in a separate process to detect application failures and recover or restart as | 290 | 294 | needed. | 291 | 295 |
| | 296 | +### Event: `'uncaughtExceptionMonitor'` | | 297 | +<!-- YAML | | 298 | +added: REPLACEME | | 299 | +--> | | 300 | + | | 301 | +* `err` {Error} The uncaught exception. | | 302 | +* `origin` {string} Indicates if the exception originates from an unhandled | | 303 | + rejection or from synchronous errors. Can either be `'uncaughtException'` or | | 304 | + `'unhandledRejection'`. | | 305 | + | | 306 | +The `'uncaughtExceptionMonitor'` event is emitted before an | | 307 | +`'uncaughtException'` event is emitted or a hook installed via | | 308 | +[`process.setUncaughtExceptionCaptureCallback()`][] is called. | | 309 | + | | 310 | +Installing an `'uncaughtExceptionMonitor'` listener does not change the behavior | | 311 | +once an `'uncaughtException'` event is emitted. The process will | | 312 | +still crash if no `'uncaughtException'` listener is installed. | | 313 | + | | 314 | +```js | | 315 | +process.on('uncaughtExceptionMonitor', (err, origin) => { | | 316 | + MyMonitoringTool.logSync(err, origin); | | 317 | +}); | | 318 | + | | 319 | +// Intentionally cause an exception, but don't catch it. | | 320 | +nonexistentFunc(); | | 321 | +// Still crashes Node.js | | 322 | +``` | | 323 | + | 292 | 324 | ### Event: `'unhandledRejection'` | 293 | 325 | <!-- YAML | 294 | 326 | added: v1.4.1 | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/execution.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -159,6 +159,7 @@ function createOnGlobalUncaughtException() { | 159 | 159 | } | 160 | 160 |
| 161 | 161 | const type = fromPromise ? 'unhandledRejection' : 'uncaughtException'; | | 162 | + process.emit('uncaughtExceptionMonitor', er, type); | 162 | 163 | if (exceptionHandlerState.captureFn !== null) { | 163 | 164 | exceptionHandlerState.captureFn(er); | 164 | 165 | } else if (!process.emit('uncaughtException', er, type)) { | |
Collapse file Copy file name to clipboard +10Lines changed: 10 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +// Keep the event loop alive. | | 4 | +setTimeout(() => {}, 1e6); | | 5 | + | | 6 | +process.on('uncaughtExceptionMonitor', (err) => { | | 7 | + console.log(`Monitored: ${err.message}`); | | 8 | +}); | | 9 | + | | 10 | +throw new Error('Shall exit'); |
Collapse file Copy file name to clipboard +11Lines changed: 11 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +// Keep the event loop alive. | | 4 | +setTimeout(() => {}, 1e6); | | 5 | + | | 6 | +process.on('uncaughtExceptionMonitor', (err) => { | | 7 | + console.log(`Monitored: ${err.message}, will throw now`); | | 8 | + missingFunction(); | | 9 | +}); | | 10 | + | | 11 | +throw new Error('Shall exit'); |
Collapse file Copy file name to clipboard +69Lines changed: 69 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +const common = require('../common'); | | 4 | +const assert = require('assert'); | | 5 | +const { execFile } = require('child_process'); | | 6 | +const fixtures = require('../common/fixtures'); | | 7 | + | | 8 | +{ | | 9 | + // Verify exit behavior is unchanged | | 10 | + const fixture = fixtures.path('uncaught-exceptions', 'uncaught-monitor1.js'); | | 11 | + execFile( | | 12 | + process.execPath, | | 13 | + [fixture], | | 14 | + common.mustCall((err, stdout, stderr) => { | | 15 | + assert.strictEqual(err.code, 1); | | 16 | + assert.strictEqual(Object.getPrototypeOf(err).name, 'Error'); | | 17 | + assert.strictEqual(stdout, 'Monitored: Shall exit\n'); | | 18 | + const errLines = stderr.trim().split(/[\r\n]+/); | | 19 | + const errLine = errLines.find((l) => /^Error/.exec(l)); | | 20 | + assert.strictEqual(errLine, 'Error: Shall exit'); | | 21 | + }) | | 22 | + ); | | 23 | +} | | 24 | + | | 25 | +{ | | 26 | + // Verify exit behavior is unchanged | | 27 | + const fixture = fixtures.path('uncaught-exceptions', 'uncaught-monitor2.js'); | | 28 | + execFile( | | 29 | + process.execPath, | | 30 | + [fixture], | | 31 | + common.mustCall((err, stdout, stderr) => { | | 32 | + assert.strictEqual(err.code, 7); | | 33 | + assert.strictEqual(Object.getPrototypeOf(err).name, 'Error'); | | 34 | + assert.strictEqual(stdout, 'Monitored: Shall exit, will throw now\n'); | | 35 | + const errLines = stderr.trim().split(/[\r\n]+/); | | 36 | + const errLine = errLines.find((l) => /^ReferenceError/.exec(l)); | | 37 | + assert.strictEqual( | | 38 | + errLine, | | 39 | + 'ReferenceError: missingFunction is not defined' | | 40 | + ); | | 41 | + }) | | 42 | + ); | | 43 | +} | | 44 | + | | 45 | +const theErr = new Error('MyError'); | | 46 | + | | 47 | +process.on( | | 48 | + 'uncaughtExceptionMonitor', | | 49 | + common.mustCall((err, origin) => { | | 50 | + assert.strictEqual(err, theErr); | | 51 | + assert.strictEqual(origin, 'uncaughtException'); | | 52 | + }, 2) | | 53 | +); | | 54 | + | | 55 | +process.on('uncaughtException', common.mustCall((err, origin) => { | | 56 | + assert.strictEqual(origin, 'uncaughtException'); | | 57 | + assert.strictEqual(err, theErr); | | 58 | +})); | | 59 | + | | 60 | +process.nextTick(common.mustCall(() => { | | 61 | + // Test with uncaughtExceptionCaptureCallback installed | | 62 | + process.setUncaughtExceptionCaptureCallback(common.mustCall( | | 63 | + (err) => assert.strictEqual(err, theErr)) | | 64 | + ); | | 65 | + | | 66 | + throw theErr; | | 67 | +})); | | 68 | + | | 69 | +throw theErr; |
|