Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

process: add 'warning' event #4782

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/cli.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ Print stack traces for deprecations.

Throw errors for deprecations.

### `--no-warnings`

Silence all process warnings (including deprecations).

### `--trace-warnings`

Print stack traces for process warnings (including deprecations).

### `--trace-sync-io`

Expand Down
204 changes: 203 additions & 1 deletion doc/api/process.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,120 @@ this, you can either attach a dummy `.catch(() => { })` handler to
`resource.loaded`, preventing the `'unhandledRejection'` event from being
emitted, or you can use the [`'rejectionHandled'`][] event.

## Event: 'warning'

Emitted whenever Node.js emits a process warning.

A process warning is similar to an error in that it describes exceptional
conditions that are being brought to the user's attention. However, warnings
are not part of the normal Node.js and JavaScript error handling flow.
Node.js can emit warnings whenever it detects bad coding practices that could
lead to sub-optimal application performance, bugs or security vulnerabilities.

The event handler for `'warning'` events is called with a single `warning`
argument whose value is an `Error` object. There are three key properties that
describe the warning:

* `name` - The name of the warning (currently `Warning` by default).
* `message` - A system-provided description of the warning.
* `stack` - A stack trace to the location in the code where the warning was
issued.

```js
process.on('warning', (warning) => {
console.warn(warning.name); // Print the warning name
console.warn(warning.message); // Print the warning message
console.warn(warning.stack); // Print the stack trace
});
```

By default, Node.js will print process warnings to `stderr`. The `--no-warnings`
command-line option can be used to suppress the default console output but the
`'warning'` event will still be emitted by the `process` object.

The following example illustrates the warning that is printed to `stderr` when
too many listeners have been added to an event

```
$ node
> event.defaultMaxListeners = 1;
> process.on('foo', () => {});
> process.on('foo', () => {});
> (node:38638) Warning: Possible EventEmitter memory leak detected. 2 foo
... listeners added. Use emitter.setMaxListeners() to increase limit
```

In contrast, the following example turns off the default warning output and
adds a custom handler to the `'warning'` event:

```
$ node --no-warnings
> var p = process.on('warning', (warning) => console.warn('Do not do that!'));
> event.defaultMaxListeners = 1;
> process.on('foo', () => {});
> process.on('foo', () => {});
> Do not do that!
```

The `--trace-warnings` command-line option can be used to have the default
console output for warnings include the full stack trace of the warning.

### Emitting custom warnings

The [`process.emitWarning()`][process_emit_warning] method can be used to issue
custom or application specific warnings.

```js
// Emit a warning using a string...
process.emitWarning('Something happened!');
// Prints: (node 12345) Warning: Something happened!

// Emit a warning using an object...
process.emitWarning('Something Happened!', 'CustomWarning');
// Prints: (node 12345) CustomWarning: Something happened!

// Emit a warning using a custom Error object...
class CustomWarning extends Error {
constructor(message) {
super(message);
this.name = 'CustomWarning';
Error.captureStackTrace(this, CustomWarning);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this extend our Warning class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no Warning class anymore :-)

const myWarning = new CustomWarning('Something happened!');
process.emitWarning(myWarning);
// Prints: (node 12345) CustomWarning: Something happened!
```

### Emitting custom deprecation warnings

Custom deprecation warnings can be emitted by setting the `name` of a custom
warning to `DeprecationWarning`. For instance:

```js
process.emitWarning('This API is deprecated', 'DeprecationWarning');
```

Or,

```js
const err = new Error('This API is deprecated');
err.name = 'DeprecationWarning';
process.emitWarning(err);
```

Launching Node.js using the `--throw-deprecation` command line flag will
cause custom deprecation warnings to be thrown as exceptions.

Using the `--trace-deprecation` command line flag will cause the custom
deprecation to be printed to `stderr` along with the stack trace.

Using the `--no-deprecation` command line flag will suppress all reporting
of the custom deprecation.

The `*-deprecation` command line flags only affect warnings that use the name
`DeprecationWarning`.

## Exit Codes

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

## process.emitWarning(warning[, name][, ctor])

* `warning` {String | Error} The warning to emit.
* `name` {String} When `warning` is a String, `name` is the name to use
for the warning. Default: `Warning`.
* `ctor` {Function} When `warning` is a String, `ctor` is an optional
function used to limit the generated stack trace. Default
`process.emitWarning`

The `process.emitWarning()` method can be used to emit custom or application
specific process warnings. These can be listened for by adding a handler to the
[`process.on('warning')`][process_warning] event.

```js
// Emit a warning using a string...
process.emitWarning('Something happened!');
// Emits: (node: 56338) Warning: Something happened!
```

```
// Emit a warning using a string and a name...
process.emitWarning('Something Happened!', 'CustomWarning');
// Emits: (node:56338) CustomWarning: Something Happened!
```

In each of the previous examples, an `Error` object is generated internally by
`process.emitWarning()` and passed through to the
[`process.on('warning')`][process_warning] event.

```
process.on('warning', (warning) => {
console.warn(warning.name);
console.warn(warning.message);
console.warn(warning.stack);
});
```

If `warning` is passed as an `Error` object, it will be passed through to the
`process.on('warning')` event handler unmodified (and the optional `name`
and `ctor` arguments will be ignored):

```
// Emit a warning using an Error object...
const myWarning = new Error('Warning! Something happened!');
myWarning.name = 'CustomWarning';

process.emitWarning(myWarning);
// Emits: (node:56338) CustomWarning: Warning! Something Happened!
```

A `TypeError` is thrown if `warning` is anything other than a string or `Error`
object.

Note that while process warnings use `Error` objects, the process warning
mechanism is **not** a replacement for normal error handling mechanisms.

The following additional handling is implemented if the warning `name` is
`DeprecationWarning`:

* If the `--throw-deprecation` command-line flag is used, the deprecation
warning is thrown as an exception rather than being emitted as an event.
* If the `--no-deprecation` command-line flag is used, the deprecation
warning is suppressed.
* If the `--trace-deprecation` command-line flag is used, the deprecation
warning is printed to `stderr` along with the full stack trace.

### Avoiding duplicate warnings

As a best practice, warnings should be emitted only once per process. To do
so, it is recommended to place the `emitWarning()` behind a simple boolean
flag as illustrated in the example below:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a built-in emitOnce?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I essentially had that previously but removed it at @rvagg's urging. Doing it this way is a bit less complicated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I can see having an emitOnce get complicated, especially since the warnings have a stack trace with them are not just a message. If emitOnce were to exist and just key off the message, then anything in userland can conflict and mask other warnings.


```
var warned = false;
function emitMyWarning() {
if (!warned) {
process.emitWarning('Only warn once!');
warned = true;
}
}
emitMyWarning();
// Emits: (node: 56339) Warning: Only warn once!
emitMyWarning();
// Emits nothing
```

## process.execArgv

This is the set of Node.js-specific command line options from the
Expand Down Expand Up @@ -1098,4 +1298,6 @@ Will print something like:
[Signal Events]: #process_signal_events
[Stream compatibility]: stream.html#stream_compatibility_with_older_node_js_versions
[the tty docs]: tty.html#tty_tty
[`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
[`JSON.stringify()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
[process_warning]: #process_event_warning
[process_emit_warning]: #process_emitwarning_warning_name_ctor
8 changes: 8 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ Print stack traces for deprecations.
.BR \-\-throw\-deprecation
Throw errors for deprecations.

.TP
.BR \-\-no\-warnings
Silence all process warnings (including deprecations).

.TP
.BR \-\-trace\-warnings
Print stack traces for process warnings (including deprecations).

.TP
.BR \-\-trace\-sync\-io
Prints a stack trace whenever synchronous I/O is detected after the first turn
Expand Down
12 changes: 3 additions & 9 deletions lib/events.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';

var internalUtil;
var domain;

function EventEmitter() {
Expand Down Expand Up @@ -246,14 +245,9 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
m = $getMaxListeners(this);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
if (!internalUtil)
internalUtil = require('internal/util');

internalUtil.error('warning: possible EventEmitter memory ' +
'leak detected. %d %s listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
existing.length, type);
console.trace();
process.emitWarning('Possible EventEmitter memory leak detected. ' +
`${existing.length} ${type} listeners added. ` +
'Use emitter.setMaxListeners() to increase limit');
}
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

_process.setup_hrtime();
_process.setupConfig(NativeModule._source);
NativeModule.require('internal/process/warning').setup();
NativeModule.require('internal/process/next_tick').setup();
NativeModule.require('internal/process/stdio').setup();
_process.setupKillAndExit();
Expand Down
49 changes: 49 additions & 0 deletions lib/internal/process/warning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict';

const traceWarnings = process.traceProcessWarnings;
const noDeprecation = process.noDeprecation;
const traceDeprecation = process.traceDeprecation;
const throwDeprecation = process.throwDeprecation;
const prefix = `(${process.release.name}:${process.pid}) `;

exports.setup = setupProcessWarnings;

function setupProcessWarnings() {
if (!process.noProcessWarnings) {
process.on('warning', (warning) => {
if (!(warning instanceof Error)) return;
const isDeprecation = warning.name === 'DeprecationWarning';
if (isDeprecation && noDeprecation) return;
const trace = traceWarnings || (isDeprecation && traceDeprecation);
if (trace && warning.stack) {
console.error(`${prefix}${warning.stack}`);
} else {
var toString = warning.toString;
if (typeof toString !== 'function')
toString = Error.prototype.toString;
console.error(`${prefix}${toString.apply(warning)}`);
}
});
}

// process.emitWarning(error)
// process.emitWarning(str[, name][, ctor])
process.emitWarning = function(warning, name, ctor) {
if (typeof name === 'function') {
ctor = name;
name = 'Warning';
}
if (warning === undefined || typeof warning === 'string') {
warning = new Error(warning);
warning.name = name || 'Warning';
Error.captureStackTrace(warning, ctor || process.emitWarning);
}
if (!(warning instanceof Error)) {
throw new TypeError('\'warning\' must be an Error object or string.');
}
if (throwDeprecation && warning.name === 'DeprecationWarning')
throw warning;
else
process.nextTick(() => process.emit('warning', warning));
};
}
30 changes: 9 additions & 21 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@

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

exports.getHiddenValue = binding.getHiddenValue;
exports.setHiddenValue = binding.setHiddenValue;

// All the internal deprecations have to use this function only, as this will
// prepend the prefix to the actual message.
exports.deprecate = function(fn, msg) {
return exports._deprecate(fn, `${prefix}${msg}`);
return exports._deprecate(fn, msg);
};

// All the internal deprecations have to use this function only, as this will
// prepend the prefix to the actual message.
exports.printDeprecationMessage = function(msg, warned) {
return exports._printDeprecationMessage(`${prefix}${msg}`, warned);
exports.printDeprecationMessage = function(msg, warned, ctor) {
if (warned || noDeprecation)
return true;
process.emitWarning(msg, 'DeprecationWarning',
ctor || exports.printDeprecationMessage);
return true;
};

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

exports._printDeprecationMessage = function(msg, warned) {
if (process.noDeprecation)
return true;

if (warned)
return warned;

if (process.throwDeprecation)
throw new Error(msg);
else if (process.traceDeprecation)
console.trace(msg.startsWith(prefix) ? msg.replace(prefix, '') : msg);
else
console.error(msg);

return true;
};

// Mark that a method should not be used.
// Returns a modified function which warns once by default.
// If --no-deprecation is set, then it is a no-op.
Expand All @@ -69,7 +57,7 @@ exports._deprecate = function(fn, msg) {

var warned = false;
function deprecated() {
warned = exports._printDeprecationMessage(msg, warned);
warned = exports.printDeprecationMessage(msg, warned, deprecated);
return fn.apply(this, arguments);
}

Expand Down
Loading