Skip to content

Commit 16802c0

Browse files
jasnellMylesBorins
authored andcommitted
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. Backport-PR-URL: #12677 PR-URL: #10116 Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: Michal Zasso <targos@protonmail.com> Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
1 parent 2d3e735 commit 16802c0

File tree

8 files changed

+182
-2
lines changed

8 files changed

+182
-2
lines changed

doc/api/cli.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ added: v6.0.0
122122

123123
Print stack traces for process warnings (including deprecations).
124124

125+
### `--redirect-warnings=file`
126+
<!-- YAML
127+
added: REPLACEME
128+
-->
129+
130+
Write process warnings to the given file instead of printing to stderr. The
131+
file will be created if it does not exist, and will be appended to if it does.
132+
If an error occurs while attempting to write the warning to the file, the
133+
warning will be written to stderr instead.
134+
125135
### `--trace-sync-io`
126136
<!-- YAML
127137
added: v2.1.0
@@ -383,6 +393,17 @@ Note: Be aware that unless the child environment is explicitly set, this
383393
evironment variable will be inherited by any child processes, and if they use
384394
OpenSSL, it may cause them to trust the same CAs as node.
385395

396+
### `NODE_REDIRECT_WARNINGS=file`
397+
<!-- YAML
398+
added: REPLACEME
399+
-->
400+
401+
When set, process warnings will be emitted to the given file instead of
402+
printing to stderr. The file will be created if it does not exist, and will be
403+
appended to if it does. If an error occurs while attempting to write the
404+
warning to the file, the warning will be written to stderr instead. This is
405+
equivalent to using the `--redirect-warnings=file` command-line flag.
406+
386407
[emit_warning]: process.html#process_process_emitwarning_warning_name_ctor
387408
[Buffer]: buffer.html#buffer_buffer
388409
[debugger]: debugger.html

doc/node.1

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ Silence all process warnings (including deprecations).
112112
.BR \-\-trace\-warnings
113113
Print stack traces for process warnings (including deprecations).
114114

115+
.TP
116+
.BR \-\-redirect\-warnings=\fIfile\fR
117+
Write process warnings to the given file instead of printing to stderr.
118+
115119
.TP
116120
.BR \-\-trace\-sync\-io
117121
Print a stack trace whenever synchronous I/O is detected after the first turn
@@ -255,6 +259,12 @@ containing trusted certificates.
255259
If \fB\-\-use\-openssl\-ca\fR is enabled, this overrides and sets OpenSSL's
256260
file containing trusted certificates.
257261

262+
.TP
263+
.BR NODE_REDIRECT_WARNINGS=\fIfile\fR
264+
Write process warnings to the given file instead of printing to stderr.
265+
(equivalent to using the \-\-redirect\-warnings=\fIfile\fR command-line
266+
argument).
267+
258268
.SH BUGS
259269
Bugs are tracked in GitHub Issues:
260270
.ur https://github.com/nodejs/node/issues

lib/internal/process/warning.js

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,81 @@ const traceWarnings = process.traceProcessWarnings;
44
const noDeprecation = process.noDeprecation;
55
const traceDeprecation = process.traceDeprecation;
66
const throwDeprecation = process.throwDeprecation;
7+
const config = process.binding('config');
78
const prefix = `(${process.release.name}:${process.pid}) `;
89

910
exports.setup = setupProcessWarnings;
1011

12+
var fs;
13+
var cachedFd;
14+
var acquiringFd = false;
15+
function nop() {}
16+
17+
function lazyFs() {
18+
if (!fs)
19+
fs = require('fs');
20+
return fs;
21+
}
22+
23+
function writeOut(message) {
24+
if (console && typeof console.error === 'function')
25+
return console.error(message);
26+
process._rawDebug(message);
27+
}
28+
29+
function onClose(fd) {
30+
return function() {
31+
lazyFs().close(fd, nop);
32+
};
33+
}
34+
35+
function onOpen(cb) {
36+
return function(err, fd) {
37+
acquiringFd = false;
38+
if (fd !== undefined) {
39+
cachedFd = fd;
40+
process.on('exit', onClose(fd));
41+
}
42+
cb(err, fd);
43+
process.emit('_node_warning_fd_acquired', err, fd);
44+
};
45+
}
46+
47+
function onAcquired(message) {
48+
// make a best effort attempt at writing the message
49+
// to the fd. Errors are ignored at this point.
50+
return function(err, fd) {
51+
if (err)
52+
return writeOut(message);
53+
lazyFs().appendFile(fd, `${message}\n`, nop);
54+
};
55+
}
56+
57+
function acquireFd(cb) {
58+
if (cachedFd === undefined && !acquiringFd) {
59+
acquiringFd = true;
60+
lazyFs().open(config.warningFile, 'a', onOpen(cb));
61+
} else if (cachedFd !== undefined && !acquiringFd) {
62+
cb(null, cachedFd);
63+
} else {
64+
process.once('_node_warning_fd_acquired', cb);
65+
}
66+
}
67+
68+
function output(message) {
69+
if (typeof config.warningFile === 'string') {
70+
acquireFd(onAcquired(message));
71+
return;
72+
}
73+
writeOut(message);
74+
}
75+
76+
function doEmitWarning(warning) {
77+
return function() {
78+
process.emit('warning', warning);
79+
};
80+
}
81+
1182
function setupProcessWarnings() {
1283
if (!process.noProcessWarnings && process.env.NODE_NO_WARNINGS !== '1') {
1384
process.on('warning', (warning) => {
@@ -21,7 +92,7 @@ function setupProcessWarnings() {
2192
var toString = warning.toString;
2293
if (typeof toString !== 'function')
2394
toString = Error.prototype.toString;
24-
console.error(`${prefix}${toString.apply(warning)}`);
95+
output(`${prefix}${toString.apply(warning)}`);
2596
}
2697
});
2798
}
@@ -44,6 +115,6 @@ function setupProcessWarnings() {
44115
if (throwDeprecation && warning.name === 'DeprecationWarning')
45116
throw warning;
46117
else
47-
process.nextTick(() => process.emit('warning', warning));
118+
process.nextTick(doEmitWarning(warning));
48119
};
49120
}

src/node.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,16 @@ bool trace_warnings = false;
199199
// that is used by lib/module.js
200200
bool config_preserve_symlinks = false;
201201

202+
202203
// Set in node.cc by ParseArgs when --expose-internals or --expose_internals is
203204
// used.
204205
// Used in node_config.cc to set a constant on process.binding('config')
205206
// that is used by lib/internal/bootstrap_node.js
206207
bool config_expose_internals = false;
207208

209+
// Set in node.cc by ParseArgs when --redirect-warnings= is used.
210+
const char* config_warning_file;
211+
208212
// process-relative uptime base, initialized at start-up
209213
static double prog_start_time;
210214
static bool debugger_running;
@@ -3701,6 +3705,8 @@ static void PrintHelp() {
37013705
"function is used\n"
37023706
" --no-warnings silence all process warnings\n"
37033707
" --trace-warnings show stack traces on process warnings\n"
3708+
" --redirect-warnings=path\n"
3709+
" write warnings to path instead of stderr\n"
37043710
" --trace-sync-io show stack trace when use of sync IO\n"
37053711
" is detected after the first tick\n"
37063712
" --track-heap-objects track heap object allocations for heap "
@@ -3765,6 +3771,7 @@ static void PrintHelp() {
37653771
" prefixed to the module search path\n"
37663772
"NODE_REPL_HISTORY path to the persistent REPL history file\n"
37673773
"OPENSSL_CONF load OpenSSL configuration from file\n"
3774+
"NODE_REDIRECT_WARNINGS write warnings to path instead of stderr\n"
37683775
"\n"
37693776
"Documentation can be found at https://nodejs.org/\n");
37703777
}
@@ -3866,6 +3873,8 @@ static void ParseArgs(int* argc,
38663873
no_process_warnings = true;
38673874
} else if (strcmp(arg, "--trace-warnings") == 0) {
38683875
trace_warnings = true;
3876+
} else if (strncmp(arg, "--redirect-warnings=", 20) == 0) {
3877+
config_warning_file = arg + 20;
38693878
} else if (strcmp(arg, "--trace-deprecation") == 0) {
38703879
trace_deprecation = true;
38713880
} else if (strcmp(arg, "--trace-sync-io") == 0) {
@@ -4401,6 +4410,10 @@ void Init(int* argc,
44014410
if (openssl_config.empty())
44024411
SafeGetenv("OPENSSL_CONF", &openssl_config);
44034412

4413+
if (auto redirect_warnings = secure_getenv("NODE_REDIRECT_WARNINGS")) {
4414+
config_warning_file = redirect_warnings;
4415+
}
4416+
44044417
// Parse a few arguments which are specific to Node.
44054418
int v8_argc;
44064419
const char** v8_argv;

src/node_config.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ using v8::Context;
1212
using v8::Local;
1313
using v8::Object;
1414
using v8::ReadOnly;
15+
using v8::String;
1516
using v8::Value;
1617

1718
// The config binding is used to provide an internal view of compile or runtime
@@ -47,6 +48,15 @@ void InitConfig(Local<Object> target,
4748

4849
if (config_expose_internals)
4950
READONLY_BOOLEAN_PROPERTY("exposeInternals");
51+
52+
if (config_warning_file != nullptr) {
53+
Local<String> name = OneByteString(env->isolate(), "warningFile");
54+
Local<String> value = String::NewFromUtf8(env->isolate(),
55+
config_warning_file,
56+
v8::NewStringType::kNormal)
57+
.ToLocalChecked();
58+
target->DefineOwnProperty(env->context(), name, value).FromJust();
59+
}
5060
} // InitConfig
5161

5262
} // namespace node

src/node_internals.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ extern bool config_preserve_symlinks;
4949
// that is used by lib/internal/bootstrap_node.js
5050
extern bool config_expose_internals;
5151

52+
// Set in node.cc by ParseArgs when --redirect-warnings= is used.
53+
// Used to redirect warning output to a file rather than sending
54+
// it to stderr.
55+
extern const char* config_warning_file;
56+
5257
// Forward declaration
5358
class Environment;
5459

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
// Tests the NODE_REDIRECT_WARNINGS environment variable by spawning
4+
// a new child node process that emits a warning into a temporary
5+
// warnings file. Once the process completes, the warning file is
6+
// opened and the contents are validated
7+
8+
const common = require('../common');
9+
const fs = require('fs');
10+
const fork = require('child_process').fork;
11+
const path = require('path');
12+
const assert = require('assert');
13+
14+
common.refreshTmpDir();
15+
16+
const warnmod = require.resolve(common.fixturesDir + '/warnings.js');
17+
const warnpath = path.join(common.tmpDir, 'warnings.txt');
18+
19+
fork(warnmod, {env: {NODE_REDIRECT_WARNINGS: warnpath}})
20+
.on('exit', common.mustCall(() => {
21+
fs.readFile(warnpath, 'utf8', common.mustCall((err, data) => {
22+
assert.ifError(err);
23+
assert(/\(node:\d+\) Warning: a bad practice warning/.test(data));
24+
}));
25+
}));
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
// Tests the --redirect-warnings command line flag by spawning
4+
// a new child node process that emits a warning into a temporary
5+
// warnings file. Once the process completes, the warning file is
6+
// opened and the contents are validated
7+
8+
const common = require('../common');
9+
const fs = require('fs');
10+
const fork = require('child_process').fork;
11+
const path = require('path');
12+
const assert = require('assert');
13+
14+
common.refreshTmpDir();
15+
16+
const warnmod = require.resolve(common.fixturesDir + '/warnings.js');
17+
const warnpath = path.join(common.tmpDir, 'warnings.txt');
18+
19+
fork(warnmod, {execArgv: [`--redirect-warnings=${warnpath}`]})
20+
.on('exit', common.mustCall(() => {
21+
fs.readFile(warnpath, 'utf8', common.mustCall((err, data) => {
22+
assert.ifError(err);
23+
assert(/\(node:\d+\) Warning: a bad practice warning/.test(data));
24+
}));
25+
}));

0 commit comments

Comments
 (0)