From 03e89b3ff298c63e9620f1547094f7fca76edde7 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 4 Dec 2016 10:38:35 -0800 Subject: [PATCH] process: add --redirect-warnings command line argument The --redirect-warnings command line argument allows process warnings to be written to a specified file rather than printed to stderr. Also adds an equivalent NODE_REDIRECT_WARNINGS environment variable. If the specified file cannot be opened or written to for any reason, the argument is ignored and the warning is printed to stderr. If the file already exists, it will be appended to. PR-URL: https://github.com/nodejs/node/pull/10116 Reviewed-By: Michael Dawson Reviewed-By: Michal Zasso Reviewed-By: Fedor Indutny --- doc/api/cli.md | 21 +++++ doc/node.1 | 10 +++ lib/internal/process/warning.js | 82 +++++++++++++++++-- src/node.cc | 14 ++++ src/node_config.cc | 10 +++ src/node_internals.h | 5 ++ .../test-process-redirect-warnings-env.js | 25 ++++++ .../test-process-redirect-warnings.js | 25 ++++++ 8 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 test/parallel/test-process-redirect-warnings-env.js create mode 100644 test/parallel/test-process-redirect-warnings.js diff --git a/doc/api/cli.md b/doc/api/cli.md index 61e1ba18948312..0c83aa12b485a1 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -121,6 +121,16 @@ added: v6.0.0 Print stack traces for process warnings (including deprecations). +### `--redirect-warnings=file` + + +Write process warnings to the given file instead of printing to stderr. The +file will be created if it does not exist, and will be appended to if it does. +If an error occurs while attempting to write the warning to the file, the +warning will be written to stderr instead. + ### `--trace-sync-io` + +When set, process warnings will be emitted to the given file instead of +printing to stderr. The file will be created if it does not exist, and will be +appended to if it does. If an error occurs while attempting to write the +warning to the file, the warning will be written to stderr instead. This is +equivalent to using the `--redirect-warnings=file` command-line flag. + [emit_warning]: process.html#process_process_emitwarning_warning_name_ctor [Buffer]: buffer.html#buffer_buffer [debugger]: debugger.html diff --git a/doc/node.1 b/doc/node.1 index a79cbcaef65933..fe1b0e7fa1aba3 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -112,6 +112,10 @@ Silence all process warnings (including deprecations). .BR \-\-trace\-warnings Print stack traces for process warnings (including deprecations). +.TP +.BR \-\-redirect\-warnings=\fIfile\fR +Write process warnings to the given file instead of printing to stderr. + .TP .BR \-\-trace\-sync\-io Print a stack trace whenever synchronous I/O is detected after the first turn @@ -262,6 +266,12 @@ containing trusted certificates. If \fB\-\-use\-openssl\-ca\fR is enabled, this overrides and sets OpenSSL's file containing trusted certificates. +.TP +.BR NODE_REDIRECT_WARNINGS=\fIfile\fR +Write process warnings to the given file instead of printing to stderr. +(equivalent to using the \-\-redirect\-warnings=\fIfile\fR command-line +argument). + .SH BUGS Bugs are tracked in GitHub Issues: .ur https://github.com/nodejs/node/issues diff --git a/lib/internal/process/warning.js b/lib/internal/process/warning.js index fd9e7e72a40430..bf487f38a417c4 100644 --- a/lib/internal/process/warning.js +++ b/lib/internal/process/warning.js @@ -1,9 +1,80 @@ 'use strict'; +const config = process.binding('config'); const prefix = `(${process.release.name}:${process.pid}) `; exports.setup = setupProcessWarnings; +var fs; +var cachedFd; +var acquiringFd = false; +function nop() {} + +function lazyFs() { + if (!fs) + fs = require('fs'); + return fs; +} + +function writeOut(message) { + if (console && typeof console.error === 'function') + return console.error(message); + process._rawDebug(message); +} + +function onClose(fd) { + return function() { + lazyFs().close(fd, nop); + }; +} + +function onOpen(cb) { + return function(err, fd) { + acquiringFd = false; + if (fd !== undefined) { + cachedFd = fd; + process.on('exit', onClose(fd)); + } + cb(err, fd); + process.emit('_node_warning_fd_acquired', err, fd); + }; +} + +function onAcquired(message) { + // make a best effort attempt at writing the message + // to the fd. Errors are ignored at this point. + return function(err, fd) { + if (err) + return writeOut(message); + lazyFs().appendFile(fd, `${message}\n`, nop); + }; +} + +function acquireFd(cb) { + if (cachedFd === undefined && !acquiringFd) { + acquiringFd = true; + lazyFs().open(config.warningFile, 'a', onOpen(cb)); + } else if (cachedFd !== undefined && !acquiringFd) { + cb(null, cachedFd); + } else { + process.once('_node_warning_fd_acquired', cb); + } +} + +function output(message) { + if (typeof config.warningFile === 'string') { + acquireFd(onAcquired(message)); + return; + } + writeOut(message); +} + +function doEmitWarning(warning) { + return function() { + process.emit('warning', warning); + }; +} + function setupProcessWarnings() { if (!process.noProcessWarnings && process.env.NODE_NO_WARNINGS !== '1') { process.on('warning', (warning) => { @@ -14,19 +85,18 @@ function setupProcessWarnings() { (isDeprecation && process.traceDeprecation); if (trace && warning.stack) { if (warning.code) { - console.error(`${prefix}[${warning.code}] ${warning.stack}`); + output(`${prefix}[${warning.code}] ${warning.stack}`); } else { - console.error(`${prefix}${warning.stack}`); + output(`${prefix}${warning.stack}`); } } else { const toString = typeof warning.toString === 'function' ? warning.toString : Error.prototype.toString; if (warning.code) { - console.error( - `${prefix}[${warning.code}] ${toString.apply(warning)}`); + output(`${prefix}[${warning.code}] ${toString.apply(warning)}`); } else { - console.error(`${prefix}${toString.apply(warning)}`); + output(`${prefix}${toString.apply(warning)}`); } } }); @@ -63,6 +133,6 @@ function setupProcessWarnings() { if (process.throwDeprecation) throw warning; } - process.nextTick(() => process.emit('warning', warning)); + process.nextTick(doEmitWarning(warning)); }; } diff --git a/src/node.cc b/src/node.cc index 51f76ecba6b251..90eee0fb9fe289 100644 --- a/src/node.cc +++ b/src/node.cc @@ -188,6 +188,9 @@ bool trace_warnings = false; // that is used by lib/module.js bool config_preserve_symlinks = false; +// Set in node.cc by ParseArgs when --redirect-warnings= is used. +const char* config_warning_file; + bool v8_initialized = false; // process-relative uptime base, initialized at start-up @@ -3499,6 +3502,9 @@ static void PrintHelp() { " --throw-deprecation throw an exception on deprecations\n" " --no-warnings silence all process warnings\n" " --trace-warnings show stack traces on process warnings\n" + " --redirect-warnings=path\n" + " write warnings to path instead of\n" + " stderr\n" " --trace-sync-io show stack trace when use of sync IO\n" " is detected after the first tick\n" " --trace-events-enabled track trace events\n" @@ -3564,6 +3570,8 @@ static void PrintHelp() { " prefixed to the module search path\n" "NODE_REPL_HISTORY path to the persistent REPL history\n" " file\n" + "NODE_REDIRECT_WARNINGS write warnings to path instead of\n" + " stderr\n" "Documentation can be found at https://nodejs.org/\n"); } @@ -3664,6 +3672,8 @@ static void ParseArgs(int* argc, no_process_warnings = true; } else if (strcmp(arg, "--trace-warnings") == 0) { trace_warnings = true; + } else if (strncmp(arg, "--redirect-warnings=", 20) == 0) { + config_warning_file = arg + 20; } else if (strcmp(arg, "--trace-deprecation") == 0) { trace_deprecation = true; } else if (strcmp(arg, "--trace-sync-io") == 0) { @@ -4206,6 +4216,10 @@ void Init(int* argc, config_preserve_symlinks = (*preserve_symlinks == '1'); } + if (auto redirect_warnings = secure_getenv("NODE_REDIRECT_WARNINGS")) { + config_warning_file = redirect_warnings; + } + // Parse a few arguments which are specific to Node. int v8_argc; const char** v8_argv; diff --git a/src/node_config.cc b/src/node_config.cc index 401345f6a608be..60001207f1851b 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -12,6 +12,7 @@ using v8::Context; using v8::Local; using v8::Object; using v8::ReadOnly; +using v8::String; using v8::Value; // The config binding is used to provide an internal view of compile or runtime @@ -44,6 +45,15 @@ void InitConfig(Local target, if (config_preserve_symlinks) READONLY_BOOLEAN_PROPERTY("preserveSymlinks"); + + if (config_warning_file != nullptr) { + Local name = OneByteString(env->isolate(), "warningFile"); + Local value = String::NewFromUtf8(env->isolate(), + config_warning_file, + v8::NewStringType::kNormal) + .ToLocalChecked(); + target->DefineOwnProperty(env->context(), name, value).FromJust(); + } } // InitConfig } // namespace node diff --git a/src/node_internals.h b/src/node_internals.h index c93870968011be..7c1b79d62f09c5 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -42,6 +42,11 @@ extern const char* openssl_config; // that is used by lib/module.js extern bool config_preserve_symlinks; +// Set in node.cc by ParseArgs when --redirect-warnings= is used. +// Used to redirect warning output to a file rather than sending +// it to stderr. +extern const char* config_warning_file; + // Tells whether it is safe to call v8::Isolate::GetCurrent(). extern bool v8_initialized; diff --git a/test/parallel/test-process-redirect-warnings-env.js b/test/parallel/test-process-redirect-warnings-env.js new file mode 100644 index 00000000000000..86942dc9e88e11 --- /dev/null +++ b/test/parallel/test-process-redirect-warnings-env.js @@ -0,0 +1,25 @@ +'use strict'; + +// Tests the NODE_REDIRECT_WARNINGS environment variable by spawning +// a new child node process that emits a warning into a temporary +// warnings file. Once the process completes, the warning file is +// opened and the contents are validated + +const common = require('../common'); +const fs = require('fs'); +const fork = require('child_process').fork; +const path = require('path'); +const assert = require('assert'); + +common.refreshTmpDir(); + +const warnmod = require.resolve(common.fixturesDir + '/warnings.js'); +const warnpath = path.join(common.tmpDir, 'warnings.txt'); + +fork(warnmod, {env: {NODE_REDIRECT_WARNINGS: warnpath}}) + .on('exit', common.mustCall(() => { + fs.readFile(warnpath, 'utf8', common.mustCall((err, data) => { + assert.ifError(err); + assert(/\(node:\d+\) Warning: a bad practice warning/.test(data)); + })); + })); diff --git a/test/parallel/test-process-redirect-warnings.js b/test/parallel/test-process-redirect-warnings.js new file mode 100644 index 00000000000000..b798e41bf6b5e4 --- /dev/null +++ b/test/parallel/test-process-redirect-warnings.js @@ -0,0 +1,25 @@ +'use strict'; + +// Tests the --redirect-warnings command line flag by spawning +// a new child node process that emits a warning into a temporary +// warnings file. Once the process completes, the warning file is +// opened and the contents are validated + +const common = require('../common'); +const fs = require('fs'); +const fork = require('child_process').fork; +const path = require('path'); +const assert = require('assert'); + +common.refreshTmpDir(); + +const warnmod = require.resolve(common.fixturesDir + '/warnings.js'); +const warnpath = path.join(common.tmpDir, 'warnings.txt'); + +fork(warnmod, {execArgv: [`--redirect-warnings=${warnpath}`]}) + .on('exit', common.mustCall(() => { + fs.readFile(warnpath, 'utf8', common.mustCall((err, data) => { + assert.ifError(err); + assert(/\(node:\d+\) Warning: a bad practice warning/.test(data)); + })); + }));