Skip to content

Commit a879dc8

Browse files
jasnellsam-github
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. PR-URL: nodejs#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 7b8fcf8 commit a879dc8

File tree

8 files changed

+181
-2
lines changed

8 files changed

+181
-2
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
@@ -382,6 +392,17 @@ Note: Be aware that unless the child environment is explicitly set, this
382392
evironment variable will be inherited by any child processes, and if they use
383393
OpenSSL, it may cause them to trust the same CAs as node.
384394

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

+73-2
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

+12
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ bool trace_warnings = false;
199199
// that is used by lib/module.js
200200
bool config_preserve_symlinks = false;
201201

202+
// Set in node.cc by ParseArgs when --redirect-warnings= is used.
203+
const char* config_warning_file;
204+
202205
// process-relative uptime base, initialized at start-up
203206
static double prog_start_time;
204207
static bool debugger_running;
@@ -3683,6 +3686,8 @@ static void PrintHelp() {
36833686
"function is used\n"
36843687
" --no-warnings silence all process warnings\n"
36853688
" --trace-warnings show stack traces on process warnings\n"
3689+
" --redirect-warnings=path\n"
3690+
" write warnings to path instead of stderr\n"
36863691
" --trace-sync-io show stack trace when use of sync IO\n"
36873692
" is detected after the first tick\n"
36883693
" --track-heap-objects track heap object allocations for heap "
@@ -3747,6 +3752,7 @@ static void PrintHelp() {
37473752
" prefixed to the module search path\n"
37483753
"NODE_REPL_HISTORY path to the persistent REPL history file\n"
37493754
"OPENSSL_CONF load OpenSSL configuration from file\n"
3755+
"NODE_REDIRECT_WARNINGS write warnings to path instead of stderr\n"
37503756
"\n"
37513757
"Documentation can be found at https://nodejs.org/\n");
37523758
}
@@ -3848,6 +3854,8 @@ static void ParseArgs(int* argc,
38483854
no_process_warnings = true;
38493855
} else if (strcmp(arg, "--trace-warnings") == 0) {
38503856
trace_warnings = true;
3857+
} else if (strncmp(arg, "--redirect-warnings=", 20) == 0) {
3858+
config_warning_file = arg + 20;
38513859
} else if (strcmp(arg, "--trace-deprecation") == 0) {
38523860
trace_deprecation = true;
38533861
} else if (strcmp(arg, "--trace-sync-io") == 0) {
@@ -4383,6 +4391,10 @@ void Init(int* argc,
43834391
if (openssl_config.empty())
43844392
SafeGetenv("OPENSSL_CONF", &openssl_config);
43854393

4394+
if (auto redirect_warnings = secure_getenv("NODE_REDIRECT_WARNINGS")) {
4395+
config_warning_file = redirect_warnings;
4396+
}
4397+
43864398
// Parse a few arguments which are specific to Node.
43874399
int v8_argc;
43884400
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
@@ -43,6 +43,11 @@ extern std::string openssl_config;
4343
// that is used by lib/module.js
4444
extern bool config_preserve_symlinks;
4545

46+
// Set in node.cc by ParseArgs when --redirect-warnings= is used.
47+
// Used to redirect warning output to a file rather than sending
48+
// it to stderr.
49+
extern const char* config_warning_file;
50+
4651
// Forward declaration
4752
class Environment;
4853

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)