Skip to content

Commit 66b1ea3

Browse files
committed
process: add 'warning' event and process.emitWarning()
In several places throughout the code we write directly to stderr to report warnings (deprecation, possible eventemitter memory leak). The current design of simply dumping the text to stderr is less than ideal. This PR introduces a new "process warnings" mechanism that emits 'warning' events on the global process object. These are invoked with a `warning` argument whose value is an Error object. By default, these warnings will be printed to stderr. This can be suppressed using the `--no-warnings` and `--no-deprecation` command line flags. For warnings, the 'warning' event will still be emitted by the process, allowing applications to handle the warnings in custom ways. The existing `--no-deprecation` flag will continue to supress all deprecation output generated by the core lib. The `--trace-warnings` command line flag will tell Node.js to print the full stack trace of warnings as part of the default handling. The existing `--no-deprecation`, `--throw-deprecation` and `--trace-deprecation` flags continue to work as they currently do, but the exact output of the warning message is modified to occur on process.nextTick(). The stack trace for the warnings and deprecations preserve and point to the correct call site. A new `process.emitWarning()` API is provided to permit userland to emit warnings and deprecations using the same consistent mechanism. Test cases and documentation are included.
1 parent 57a4dd9 commit 66b1ea3

14 files changed

+393
-54
lines changed

doc/api/cli.markdown

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ Print stack traces for deprecations.
7676

7777
Throw errors for deprecations.
7878

79+
### `--no-warnings`
80+
81+
Silence all process warnings (including deprecations).
82+
83+
### `--trace-warnings`
84+
85+
Print stack traces for process warnings (including deprecations).
7986

8087
### `--trace-sync-io`
8188

doc/api/process.markdown

Lines changed: 203 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,120 @@ this, you can either attach a dummy `.catch(() => { })` handler to
186186
`resource.loaded`, preventing the `'unhandledRejection'` event from being
187187
emitted, or you can use the [`'rejectionHandled'`][] event.
188188

189+
## Event: 'warning'
190+
191+
Emitted whenever Node.js emits a process warning.
192+
193+
A process warning is similar to an error in that it describes exceptional
194+
conditions that are being brought to the user's attention. However, warnings
195+
are not part of the normal Node.js and JavaScript error handling flow.
196+
Node.js can emit warnings whenever it detects bad coding practices that could
197+
lead to sub-optimal application performance, bugs or security vulnerabilities.
198+
199+
The event handler for `'warning'` events is called with a single `warning`
200+
argument whose value is an `Error` object. There are three key properties that
201+
describe the warning:
202+
203+
* `name` - The name of the warning (currently `Warning` by default).
204+
* `message` - A system-provided description of the warning.
205+
* `stack` - A stack trace to the location in the code where the warning was
206+
issued.
207+
208+
```js
209+
process.on('warning', (warning) => {
210+
console.warn(warning.name); // Print the warning name
211+
console.warn(warning.message); // Print the warning message
212+
console.warn(warning.stack); // Print the stack trace
213+
});
214+
```
215+
216+
By default, Node.js will print process warnings to `stderr`. The `--no-warnings`
217+
command-line option can be used to suppress the default console output but the
218+
`'warning'` event will still be emitted by the `process` object.
219+
220+
The following example illustrates the warning that is printed to `stderr` when
221+
too many listeners have been added to an event
222+
223+
```
224+
$ node
225+
> event.defaultMaxListeners = 1;
226+
> process.on('foo', () => {});
227+
> process.on('foo', () => {});
228+
> (node:38638) Warning: Possible EventEmitter memory leak detected. 2 foo
229+
... listeners added. Use emitter.setMaxListeners() to increase limit
230+
```
231+
232+
In contrast, the following example turns off the default warning output and
233+
adds a custom handler to the `'warning'` event:
234+
235+
```
236+
$ node --no-warnings
237+
> var p = process.on('warning', (warning) => console.warn('Do not do that!'));
238+
> event.defaultMaxListeners = 1;
239+
> process.on('foo', () => {});
240+
> process.on('foo', () => {});
241+
> Do not do that!
242+
```
243+
244+
The `--trace-warnings` command-line option can be used to have the default
245+
console output for warnings include the full stack trace of the warning.
246+
247+
### Emitting custom warnings
248+
249+
The [`process.emitWarning()`][process_emit_warning] method can be used to issue
250+
custom or application specific warnings.
251+
252+
```js
253+
// Emit a warning using a string...
254+
process.emitWarning('Something happened!');
255+
// Prints: (node 12345) Warning: Something happened!
256+
257+
// Emit a warning using an object...
258+
process.emitWarning('Something Happened!', 'CustomWarning');
259+
// Prints: (node 12345) CustomWarning: Something happened!
260+
261+
// Emit a warning using a custom Error object...
262+
class CustomWarning extends Error {
263+
constructor(message) {
264+
super(message);
265+
this.name = 'CustomWarning';
266+
Error.captureStackTrace(this, CustomWarning);
267+
}
268+
}
269+
const myWarning = new CustomWarning('Something happened!');
270+
process.emitWarning(myWarning);
271+
// Prints: (node 12345) CustomWarning: Something happened!
272+
```
273+
274+
### Emitting custom deprecation warnings
275+
276+
Custom deprecation warnings can be emitted by setting the `name` of a custom
277+
warning to `DeprecationWarning`. For instance:
278+
279+
```js
280+
process.emitWarning('This API is deprecated', 'DeprecationWarning');
281+
```
282+
283+
Or,
284+
285+
```js
286+
const err = new Error('This API is deprecated');
287+
err.name = 'DeprecationWarning';
288+
process.emitWarning(err);
289+
```
290+
291+
Launching Node.js using the `--throw-deprecation` command line flag will
292+
cause custom deprecation warnings to be thrown as exceptions.
293+
294+
Using the `--trace-deprecation` command line flag will cause the custom
295+
deprecation to be printed to `stderr` along with the stack trace.
296+
297+
Using the `--no-deprecation` command line flag will suppress all reporting
298+
of the custom deprecation.
299+
300+
The `*-deprecation` command line flags only affect warnings that use the name
301+
`DeprecationWarning`.
302+
189303
## Exit Codes
190304

191305
Node.js will normally exit with a `0` status code when no more async
@@ -457,6 +571,92 @@ console.log(process.env.TEST);
457571
// => undefined
458572
```
459573

574+
## process.emitWarning(warning[, name][, ctor])
575+
576+
* `warning` {String | Error} The warning to emit.
577+
* `name` {String} When `warning` is a String, `name` is the name to use
578+
for the warning. Default: `Warning`.
579+
* `ctor` {Function} When `warning` is a String, `ctor` is an optional
580+
function used to limit the generated stack trace. Default
581+
`process.emitWarning`
582+
583+
The `process.emitWarning()` method can be used to emit custom or application
584+
specific process warnings. These can be listened for by adding a handler to the
585+
[`process.on('warning')`][process_warning] event.
586+
587+
```js
588+
// Emit a warning using a string...
589+
process.emitWarning('Something happened!');
590+
// Emits: (node: 56338) Warning: Something happened!
591+
```
592+
593+
```
594+
// Emit a warning using a string and a name...
595+
process.emitWarning('Something Happened!', 'CustomWarning');
596+
// Emits: (node:56338) CustomWarning: Something Happened!
597+
```
598+
599+
In each of the previous examples, an `Error` object is generated internally by
600+
`process.emitWarning()` and passed through to the
601+
[`process.on('warning')`][process_warning] event.
602+
603+
```
604+
process.on('warning', (warning) => {
605+
console.warn(warning.name);
606+
console.warn(warning.message);
607+
console.warn(warning.stack);
608+
});
609+
```
610+
611+
If `warning` is passed as an `Error` object, it will be passed through to the
612+
`process.on('warning')` event handler unmodified (and the optional `name`
613+
and `ctor` arguments will be ignored):
614+
615+
```
616+
// Emit a warning using an Error object...
617+
const myWarning = new Error('Warning! Something happened!');
618+
myWarning.name = 'CustomWarning';
619+
620+
process.emitWarning(myWarning);
621+
// Emits: (node:56338) CustomWarning: Warning! Something Happened!
622+
```
623+
624+
A `TypeError` is thrown if `warning` is anything other than a string or `Error`
625+
object.
626+
627+
Note that while process warnings use `Error` objects, the process warning
628+
mechanism is **not** a replacement for normal error handling mechanisms.
629+
630+
The following additional handling is implemented if the warning `name` is
631+
`DeprecationWarning`:
632+
633+
* If the `--throw-deprecation` command-line flag is used, the deprecation
634+
warning is thrown as an exception rather than being emitted as an event.
635+
* If the `--no-deprecation` command-line flag is used, the deprecation
636+
warning is suppressed.
637+
* If the `--trace-deprecation` command-line flag is used, the deprecation
638+
warning is printed to `stderr` along with the full stack trace.
639+
640+
### Avoiding duplicate warnings
641+
642+
As a best practice, warnings should be emitted only once per process. To do
643+
so, it is recommended to place the `emitWarning()` behind a simple boolean
644+
flag as illustrated in the example below:
645+
646+
```
647+
var warned = false;
648+
function emitMyWarning() {
649+
if (!warned) {
650+
process.emitWarning('Only warn once!');
651+
warned = true;
652+
}
653+
}
654+
emitMyWarning();
655+
// Emits: (node: 56339) Warning: Only warn once!
656+
emitMyWarning();
657+
// Emits nothing
658+
```
659+
460660
## process.execArgv
461661

462662
This is the set of Node.js-specific command line options from the
@@ -1098,4 +1298,6 @@ Will print something like:
10981298
[Signal Events]: #process_signal_events
10991299
[Stream compatibility]: stream.html#stream_compatibility_with_older_node_js_versions
11001300
[the tty docs]: tty.html#tty_tty
1101-
[`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
1301+
[`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
1302+
[process_warning]: #process_event_warning
1303+
[process_emit_warning]: #process_emitwarning_warning_name_ctor

doc/node.1

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ Print stack traces for deprecations.
7878
.BR \-\-throw\-deprecation
7979
Throw errors for deprecations.
8080

81+
.TP
82+
.BR \-\-no\-warnings
83+
Silence all process warnings (including deprecations).
84+
85+
.TP
86+
.BR \-\-trace\-warnings
87+
Print stack traces for process warnings (including deprecations).
88+
8189
.TP
8290
.BR \-\-trace\-sync\-io
8391
Prints a stack trace whenever synchronous I/O is detected after the first turn

lib/events.js

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22

3-
var internalUtil;
43
var domain;
54

65
function EventEmitter() {
@@ -246,14 +245,9 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
246245
m = $getMaxListeners(this);
247246
if (m && m > 0 && existing.length > m) {
248247
existing.warned = true;
249-
if (!internalUtil)
250-
internalUtil = require('internal/util');
251-
252-
internalUtil.error('warning: possible EventEmitter memory ' +
253-
'leak detected. %d %s listeners added. ' +
254-
'Use emitter.setMaxListeners() to increase limit.',
255-
existing.length, type);
256-
console.trace();
248+
process.emitWarning('Possible EventEmitter memory leak detected. ' +
249+
`${existing.length} ${type} listeners added. ` +
250+
'Use emitter.setMaxListeners() to increase limit');
257251
}
258252
}
259253
}

lib/internal/bootstrap_node.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
_process.setup_hrtime();
4949
_process.setupConfig(NativeModule._source);
50+
NativeModule.require('internal/process/warning').setup();
5051
NativeModule.require('internal/process/next_tick').setup();
5152
NativeModule.require('internal/process/stdio').setup();
5253
_process.setupKillAndExit();

lib/internal/process/warning.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use strict';
2+
3+
const traceWarnings = process.traceProcessWarnings;
4+
const noDeprecation = process.noDeprecation;
5+
const traceDeprecation = process.traceDeprecation;
6+
const throwDeprecation = process.throwDeprecation;
7+
const prefix = `(${process.release.name}:${process.pid}) `;
8+
9+
exports.setup = setupProcessWarnings;
10+
11+
function setupProcessWarnings() {
12+
if (!process.noProcessWarnings) {
13+
process.on('warning', (warning) => {
14+
if (!(warning instanceof Error)) return;
15+
const isDeprecation = warning.name === 'DeprecationWarning';
16+
if (isDeprecation && noDeprecation) return;
17+
const trace = traceWarnings || (isDeprecation && traceDeprecation);
18+
if (trace && warning.stack) {
19+
console.error(`${prefix}${warning.stack}`);
20+
} else {
21+
var toString = warning.toString;
22+
if (typeof toString !== 'function')
23+
toString = Error.prototype.toString;
24+
console.error(`${prefix}${toString.apply(warning)}`);
25+
}
26+
});
27+
}
28+
29+
// process.emitWarning(error)
30+
// process.emitWarning(str[, name][, ctor])
31+
process.emitWarning = function(warning, name, ctor) {
32+
if (typeof name === 'function') {
33+
ctor = name;
34+
name = 'Warning';
35+
}
36+
if (warning === undefined || typeof warning === 'string') {
37+
warning = new Error(warning);
38+
warning.name = name || 'Warning';
39+
Error.captureStackTrace(warning, ctor || process.emitWarning);
40+
}
41+
if (!(warning instanceof Error)) {
42+
throw new TypeError('\'warning\' must be an Error object or string.');
43+
}
44+
if (throwDeprecation && warning.name === 'DeprecationWarning')
45+
throw warning;
46+
else
47+
process.nextTick(() => process.emit('warning', warning));
48+
};
49+
}

lib/internal/util.js

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@
22

33
const binding = process.binding('util');
44
const prefix = `(${process.release.name}:${process.pid}) `;
5+
const noDeprecation = process.noDeprecation;
56

67
exports.getHiddenValue = binding.getHiddenValue;
78
exports.setHiddenValue = binding.setHiddenValue;
89

910
// All the internal deprecations have to use this function only, as this will
1011
// prepend the prefix to the actual message.
1112
exports.deprecate = function(fn, msg) {
12-
return exports._deprecate(fn, `${prefix}${msg}`);
13+
return exports._deprecate(fn, msg);
1314
};
1415

1516
// All the internal deprecations have to use this function only, as this will
1617
// prepend the prefix to the actual message.
17-
exports.printDeprecationMessage = function(msg, warned) {
18-
return exports._printDeprecationMessage(`${prefix}${msg}`, warned);
18+
exports.printDeprecationMessage = function(msg, warned, ctor) {
19+
if (warned || noDeprecation)
20+
return true;
21+
process.emitWarning(msg, 'DeprecationWarning',
22+
ctor || exports.printDeprecationMessage);
23+
return true;
1924
};
2025

2126
exports.error = function(msg) {
@@ -35,23 +40,6 @@ exports.trace = function(msg) {
3540
console.trace(`${prefix}${msg}`);
3641
};
3742

38-
exports._printDeprecationMessage = function(msg, warned) {
39-
if (process.noDeprecation)
40-
return true;
41-
42-
if (warned)
43-
return warned;
44-
45-
if (process.throwDeprecation)
46-
throw new Error(msg);
47-
else if (process.traceDeprecation)
48-
console.trace(msg.startsWith(prefix) ? msg.replace(prefix, '') : msg);
49-
else
50-
console.error(msg);
51-
52-
return true;
53-
};
54-
5543
// Mark that a method should not be used.
5644
// Returns a modified function which warns once by default.
5745
// If --no-deprecation is set, then it is a no-op.
@@ -69,7 +57,7 @@ exports._deprecate = function(fn, msg) {
6957

7058
var warned = false;
7159
function deprecated() {
72-
warned = exports._printDeprecationMessage(msg, warned);
60+
warned = exports.printDeprecationMessage(msg, warned, deprecated);
7361
return fn.apply(this, arguments);
7462
}
7563

0 commit comments

Comments
 (0)