Skip to content

Commit 03e89b3

Browse files
committed
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: #10116 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Michal Zasso <[email protected]> Reviewed-By: Fedor Indutny <[email protected]>
1 parent 5e1f32f commit 03e89b3

8 files changed

+186
-6
lines changed

doc/api/cli.md

+21
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ added: v6.0.0
121121

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

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

408+
### `NODE_REDIRECT_WARNINGS=file`
409+
<!-- YAML
410+
added: REPLACEME
411+
-->
412+
413+
When set, process warnings will be emitted to the given file instead of
414+
printing to stderr. The file will be created if it does not exist, and will be
415+
appended to if it does. If an error occurs while attempting to write the
416+
warning to the file, the warning will be written to stderr instead. This is
417+
equivalent to using the `--redirect-warnings=file` command-line flag.
418+
398419
[emit_warning]: process.html#process_process_emitwarning_warning_name_ctor
399420
[Buffer]: buffer.html#buffer_buffer
400421
[debugger]: debugger.html

doc/node.1

+10
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
@@ -262,6 +266,12 @@ containing trusted certificates.
262266
If \fB\-\-use\-openssl\-ca\fR is enabled, this overrides and sets OpenSSL's
263267
file containing trusted certificates.
264268

269+
.TP
270+
.BR NODE_REDIRECT_WARNINGS=\fIfile\fR
271+
Write process warnings to the given file instead of printing to stderr.
272+
(equivalent to using the \-\-redirect\-warnings=\fIfile\fR command-line
273+
argument).
274+
265275
.SH BUGS
266276
Bugs are tracked in GitHub Issues:
267277
.ur https://github.com/nodejs/node/issues

lib/internal/process/warning.js

+76-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,80 @@
11
'use strict';
22

3+
const config = process.binding('config');
34
const prefix = `(${process.release.name}:${process.pid}) `;
45

56
exports.setup = setupProcessWarnings;
67

8+
var fs;
9+
var cachedFd;
10+
var acquiringFd = false;
11+
function nop() {}
12+
13+
function lazyFs() {
14+
if (!fs)
15+
fs = require('fs');
16+
return fs;
17+
}
18+
19+
function writeOut(message) {
20+
if (console && typeof console.error === 'function')
21+
return console.error(message);
22+
process._rawDebug(message);
23+
}
24+
25+
function onClose(fd) {
26+
return function() {
27+
lazyFs().close(fd, nop);
28+
};
29+
}
30+
31+
function onOpen(cb) {
32+
return function(err, fd) {
33+
acquiringFd = false;
34+
if (fd !== undefined) {
35+
cachedFd = fd;
36+
process.on('exit', onClose(fd));
37+
}
38+
cb(err, fd);
39+
process.emit('_node_warning_fd_acquired', err, fd);
40+
};
41+
}
42+
43+
function onAcquired(message) {
44+
// make a best effort attempt at writing the message
45+
// to the fd. Errors are ignored at this point.
46+
return function(err, fd) {
47+
if (err)
48+
return writeOut(message);
49+
lazyFs().appendFile(fd, `${message}\n`, nop);
50+
};
51+
}
52+
53+
function acquireFd(cb) {
54+
if (cachedFd === undefined && !acquiringFd) {
55+
acquiringFd = true;
56+
lazyFs().open(config.warningFile, 'a', onOpen(cb));
57+
} else if (cachedFd !== undefined && !acquiringFd) {
58+
cb(null, cachedFd);
59+
} else {
60+
process.once('_node_warning_fd_acquired', cb);
61+
}
62+
}
63+
64+
function output(message) {
65+
if (typeof config.warningFile === 'string') {
66+
acquireFd(onAcquired(message));
67+
return;
68+
}
69+
writeOut(message);
70+
}
71+
72+
function doEmitWarning(warning) {
73+
return function() {
74+
process.emit('warning', warning);
75+
};
76+
}
77+
778
function setupProcessWarnings() {
879
if (!process.noProcessWarnings && process.env.NODE_NO_WARNINGS !== '1') {
980
process.on('warning', (warning) => {
@@ -14,19 +85,18 @@ function setupProcessWarnings() {
1485
(isDeprecation && process.traceDeprecation);
1586
if (trace && warning.stack) {
1687
if (warning.code) {
17-
console.error(`${prefix}[${warning.code}] ${warning.stack}`);
88+
output(`${prefix}[${warning.code}] ${warning.stack}`);
1889
} else {
19-
console.error(`${prefix}${warning.stack}`);
90+
output(`${prefix}${warning.stack}`);
2091
}
2192
} else {
2293
const toString =
2394
typeof warning.toString === 'function' ?
2495
warning.toString : Error.prototype.toString;
2596
if (warning.code) {
26-
console.error(
27-
`${prefix}[${warning.code}] ${toString.apply(warning)}`);
97+
output(`${prefix}[${warning.code}] ${toString.apply(warning)}`);
2898
} else {
29-
console.error(`${prefix}${toString.apply(warning)}`);
99+
output(`${prefix}${toString.apply(warning)}`);
30100
}
31101
}
32102
});
@@ -63,6 +133,6 @@ function setupProcessWarnings() {
63133
if (process.throwDeprecation)
64134
throw warning;
65135
}
66-
process.nextTick(() => process.emit('warning', warning));
136+
process.nextTick(doEmitWarning(warning));
67137
};
68138
}

src/node.cc

+14
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ bool trace_warnings = false;
188188
// that is used by lib/module.js
189189
bool config_preserve_symlinks = false;
190190

191+
// Set in node.cc by ParseArgs when --redirect-warnings= is used.
192+
const char* config_warning_file;
193+
191194
bool v8_initialized = false;
192195

193196
// process-relative uptime base, initialized at start-up
@@ -3499,6 +3502,9 @@ static void PrintHelp() {
34993502
" --throw-deprecation throw an exception on deprecations\n"
35003503
" --no-warnings silence all process warnings\n"
35013504
" --trace-warnings show stack traces on process warnings\n"
3505+
" --redirect-warnings=path\n"
3506+
" write warnings to path instead of\n"
3507+
" stderr\n"
35023508
" --trace-sync-io show stack trace when use of sync IO\n"
35033509
" is detected after the first tick\n"
35043510
" --trace-events-enabled track trace events\n"
@@ -3564,6 +3570,8 @@ static void PrintHelp() {
35643570
" prefixed to the module search path\n"
35653571
"NODE_REPL_HISTORY path to the persistent REPL history\n"
35663572
" file\n"
3573+
"NODE_REDIRECT_WARNINGS write warnings to path instead of\n"
3574+
" stderr\n"
35673575
"Documentation can be found at https://nodejs.org/\n");
35683576
}
35693577

@@ -3664,6 +3672,8 @@ static void ParseArgs(int* argc,
36643672
no_process_warnings = true;
36653673
} else if (strcmp(arg, "--trace-warnings") == 0) {
36663674
trace_warnings = true;
3675+
} else if (strncmp(arg, "--redirect-warnings=", 20) == 0) {
3676+
config_warning_file = arg + 20;
36673677
} else if (strcmp(arg, "--trace-deprecation") == 0) {
36683678
trace_deprecation = true;
36693679
} else if (strcmp(arg, "--trace-sync-io") == 0) {
@@ -4206,6 +4216,10 @@ void Init(int* argc,
42064216
config_preserve_symlinks = (*preserve_symlinks == '1');
42074217
}
42084218

4219+
if (auto redirect_warnings = secure_getenv("NODE_REDIRECT_WARNINGS")) {
4220+
config_warning_file = redirect_warnings;
4221+
}
4222+
42094223
// Parse a few arguments which are specific to Node.
42104224
int v8_argc;
42114225
const char** v8_argv;

src/node_config.cc

+10
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
@@ -44,6 +45,15 @@ void InitConfig(Local<Object> target,
4445

4546
if (config_preserve_symlinks)
4647
READONLY_BOOLEAN_PROPERTY("preserveSymlinks");
48+
49+
if (config_warning_file != nullptr) {
50+
Local<String> name = OneByteString(env->isolate(), "warningFile");
51+
Local<String> value = String::NewFromUtf8(env->isolate(),
52+
config_warning_file,
53+
v8::NewStringType::kNormal)
54+
.ToLocalChecked();
55+
target->DefineOwnProperty(env->context(), name, value).FromJust();
56+
}
4757
} // InitConfig
4858

4959
} // namespace node

src/node_internals.h

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ extern const char* openssl_config;
4242
// that is used by lib/module.js
4343
extern bool config_preserve_symlinks;
4444

45+
// Set in node.cc by ParseArgs when --redirect-warnings= is used.
46+
// Used to redirect warning output to a file rather than sending
47+
// it to stderr.
48+
extern const char* config_warning_file;
49+
4550
// Tells whether it is safe to call v8::Isolate::GetCurrent().
4651
extern bool v8_initialized;
4752

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+
}));
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)