Skip to content

Commit

Permalink
process: add 'warning' event
Browse files Browse the repository at this point in the history
  • Loading branch information
geek committed Feb 26, 2016
1 parent 6e6ce09 commit 84027f8
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 34 deletions.
166 changes: 166 additions & 0 deletions doc/api/process.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,84 @@ 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 errors in that they describe exceptional
conditions that are being brought to the user's attention. However, they are not,
however, part of the normal Node.js and JavaScript error handling flow. Node.js
can emit warnings whenever it detects: (a) the use of deprecated APIs, (b) bad
coding practices that could lead to sub-optimal application performance or bugs.

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 either `DeprecationWarning`,
`BadPracticeWarning`).
* `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 default deprecation warning that is
printed to `stderr` when a deprecated API is used:

```
$ node
> util.debug('foo');
DEBUG: foo
undefined
> DeprecationWarning: (node:94742) util.debug is deprecated. Use
console.error instead.
```

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('DEPRECATED API!'));
> util.debug('foo');
DEBUG: foo
undefined
> DEPRECATED API!
```

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.

### Warning Types

Node.js can produce two types of warnings:

* `BadPracticeWarning` - a warning indicating that parts of the Node.js API are
being used in a manner that is not recommended or has known issues.
* `DeprecationWarning` - a warning indicating that a deprecated feature is
being used.

### Emitting Custom Warnings

The `process.emitWarning(message, name)` method can be used issue custom or
application specific warnings.

```js
// Emit a warning using an Error object...
process.emitWarning('Warning! Something happened!', 'CustomWarning');
```

## Exit Codes

Node.js will normally exit with a `0` status code when no more async
Expand Down Expand Up @@ -395,6 +473,94 @@ Identical to the parent process's [`ChildProcess.disconnect()`][].
If Node.js was not spawned with an IPC channel, `process.disconnect()` will be
undefined.

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

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

* `warning` {String|Error} The warning to issue
* `name` {String} the name representing the warning. Defaults to 'Warning'

The `warning` argument can be either a string or an Error object. If a `name`
is supplied, it will be set on the resulting Error object, overwriting any
existing `name` property on the Error object.

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

In the previous example, an `Error` object is generated and passed
through to the [`process.emit('warning')`][process_warning] event.

```js
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.emit('warning')` event handler. If a name is also passed in, then it
will be used to set the `name` property on the Error if one doesn't already
exist:

```js
// Emit a warning using an Error object...

const myWarning = new Error('Warning! Something happened!');
myWarning.name = 'CustomWarning';

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

```js
// Emit a warning using an Error object and name...

const myWarning = new Error('Warning! Something happened!');

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

```js
// Emit a warning using a string

process.emitWarning('Warning! Something happened!', 'CustomWarning');
// Emits: CustomWarning: (node:56338) 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.

### 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:

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

## process.env

An object containing the user environment. See environ(7).
Expand Down
14 changes: 5 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,11 @@ 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();

const warning = `warning: possible EventEmitter memory ` +
`leak detected. ${existing.length} ${type} listeners added. ` +
`Use emitter.setMaxListeners() to increase limit.`;
process.emitWarning(warning, 'BadPracticeWarning');
}
}
}
Expand Down
9 changes: 5 additions & 4 deletions lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ exports._printDeprecationMessage = function(msg, 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);

if (process.traceDeprecation)
msg = msg.startsWith(prefix) ? msg.replace(prefix, '') : msg;

process.emitWarning(msg || 'deprecated', 'DeprecationWarning');

return true;
};
Expand Down
20 changes: 20 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ static const char* icu_data_dir = nullptr;
// used by C++ modules as well
bool no_deprecation = false;

// true if process warnings should be suppressed
bool no_process_warnings = false;
bool trace_warnings = false;

#if HAVE_OPENSSL && NODE_FIPS_MODE
// used by crypto module
bool enable_fips_crypto = false;
Expand Down Expand Up @@ -3027,6 +3031,16 @@ void SetupProcessObject(Environment* env,
READONLY_PROPERTY(process, "noDeprecation", True(env->isolate()));
}

// --no-warnings
if (no_process_warnings) {
READONLY_PROPERTY(process, "noProcessWarnings", True(env->isolate()));
}

// --trace-warnings
if (trace_warnings) {
READONLY_PROPERTY(process, "traceProcessWarnings", True(env->isolate()));
}

// --throw-deprecation
if (throw_deprecation) {
READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate()));
Expand Down Expand Up @@ -3277,9 +3291,11 @@ static void PrintHelp() {
" does not appear to be a terminal\n"
" -r, --require module to preload (option can be repeated)\n"
" --no-deprecation silence deprecation warnings\n"
" --no-warnings silence all process warnings\n"
" --throw-deprecation throw an exception anytime a deprecated "
"function is used\n"
" --trace-deprecation show stack traces on deprecations\n"
" --trace-warnings show stack traces on process warnings\n"
" --trace-sync-io show stack trace when use of sync IO\n"
" is detected after the first tick\n"
" --track-heap-objects track heap object allocations for heap "
Expand Down Expand Up @@ -3415,6 +3431,10 @@ static void ParseArgs(int* argc,
force_repl = true;
} else if (strcmp(arg, "--no-deprecation") == 0) {
no_deprecation = true;
} else if (strcmp(arg, "--no-warnings") == 0) {
no_process_warnings = true;
} else if (strcmp(arg, "--trace-warnings") == 0) {
trace_warnings = true;
} else if (strcmp(arg, "--trace-deprecation") == 0) {
trace_deprecation = true;
} else if (strcmp(arg, "--trace-sync-io") == 0) {
Expand Down
36 changes: 36 additions & 0 deletions src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,42 @@

EventEmitter.call(process);

// Default process warning handler..
process.on('warning', (warning) => {
// By default, print the short warning message if --no-warnings is not
// set. If --trace-warnings is set, then the stack trace for the warning
// is shown. This takes backwards compatibility with --trace-deprecation
// into consideration also.
if (process.noProcessWarnings) return;
const trace = process.traceProcessWarnings ||
(warning.name === 'DeprecationWarning' &&
process.traceDeprecation);
if (trace)
console.warn(warning.stack);
else
console.warn(`${warning.name}: ${warning.message}`);
});

// process.emitWarning(error[, name])
// process.emitWarning(str[, name])
process.emitWarning = function(warning, name) {
if (!(warning instanceof Error) && typeof warning !== 'string')
throw new TypeError(`'warning' must be an Error object or string`);

if (name && typeof name !== 'string')
throw new TypeError(`'name' must be a string`);

if (typeof warning === 'string') {
warning = new Error(warning);
}

warning.name = name || (warning.name === 'Error'
? 'Warning'
: warning.name);

process.nextTick(() => process.emit('warning', warning));
};

let eeWarned = false;
Object.defineProperty(process, 'EventEmitter', {
get() {
Expand Down
15 changes: 15 additions & 0 deletions test/fixtures/warnings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const securityWarning = new Error('a security warning');
securityWarning.name = 'SecurityWarning';
process.emit('warning', securityWarning);


const badPracticeWarning = new Error('a bad practice warning');
badPracticeWarning.name = 'BadPracticeWarning';
process.emit('warning', badPracticeWarning);


const deprecationWarning = new Error('a deprecation warning');
deprecationWarning.name = 'DeprecationWarning';
process.emit('warning', deprecationWarning);
25 changes: 16 additions & 9 deletions test/parallel/test-global-console-exists.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,28 @@
'use strict';

const assert = require('assert');
const common = require('../common');
const EventEmitter = require('events');
const leak_warning = /EventEmitter memory leak detected\. 2 hello listeners/;

var write_calls = 0;
process.stderr.write = function(data) {

process.on('warning', (warning) => {
// This will be called after the default internal
// process warning handler is called. The default
// process warning writes to the console, which will
// invoke the monkeypatched process.stderr.write
// below.
assert.strictEqual(write_calls, 1);
EventEmitter.defaultMaxListeners = old_default;
// when we get here, we should be done
});

process.stderr.write = (data) => {
if (write_calls === 0)
assert.ok(data.match(leak_warning));
else if (write_calls === 1)
assert.ok(data.match(/Trace/));
else
assert.ok(false, 'stderr.write should be called only twice');
common.fail('stderr.write should be called only once');

write_calls++;
};
Expand All @@ -27,10 +38,6 @@ const e = new EventEmitter();
e.on('hello', function() {});
e.on('hello', function() {});

// TODO: figure out how to validate console. Currently,
// TODO: Figure out how to validate console. Currently,
// there is no obvious way of validating that console
// exists here exactly when it should.

assert.equal(write_calls, 2);

EventEmitter.defaultMaxListeners = old_default;
Loading

0 comments on commit 84027f8

Please sign in to comment.