process: add --redirect-warnings command line argument · nodejs/node@03e89b3 · GitHub
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 <michael_dawson@ca.ibm.com> Reviewed-By: Michal Zasso <targos@protonmail.com> Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
1 parent 5e1f32f commit 03e89b3

8 files changed

Lines changed: 186 additions & 6 deletions

File tree

doc/api/cli.md

Lines changed: 21 additions & 0 deletions

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
@@ -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

Lines changed: 76 additions & 6 deletions
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

Lines changed: 14 additions & 0 deletions
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

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
@@ -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

Lines changed: 5 additions & 0 deletions
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

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

0 commit comments

Comments
 (0)