report: refactor configuration management
This commit removes process.report.setOptions(). Instead of using complex configuration synchronization between C++ and JS, this commit introduces individual getters and setters. PR-URL: https://github.com/nodejs/node/pull/26414 Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Wyatt Preul <wpreul@gmail.com>
This commit is contained in:
parent
c78788a021
commit
30ee27784c
@ -1669,6 +1669,21 @@ added: v11.8.0
|
||||
reports for the current process. Additional documentation is available in the
|
||||
[report documentation][].
|
||||
|
||||
## process.report.directory
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {string}
|
||||
|
||||
Directory where the report is written. The default value is the empty string,
|
||||
indicating that reports are written to the current working directory of the
|
||||
Node.js process.
|
||||
|
||||
```js
|
||||
console.log(`Report directory is ${process.report.directory}`);
|
||||
```
|
||||
|
||||
### process.report.getReport([err])
|
||||
<!-- YAML
|
||||
added: v11.8.0
|
||||
@ -1687,43 +1702,75 @@ console.log(data);
|
||||
|
||||
Additional documentation is available in the [report documentation][].
|
||||
|
||||
### process.report.setOptions([options]);
|
||||
## process.report.filename
|
||||
<!-- YAML
|
||||
added: v11.8.0
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `options` {Object}
|
||||
* `events` {string[]}
|
||||
* `signal`: Generate a report in response to a signal raised on the process.
|
||||
* `exception`: Generate a report on unhandled exceptions.
|
||||
* `fatalerror`: Generate a report on internal fault
|
||||
(such as out of memory errors or native assertions).
|
||||
* `signal` {string} Sets or resets the signal for report generation
|
||||
(not supported on Windows). **Default:** `'SIGUSR2'`.
|
||||
* `filename` {string} Name of the file where the report is written.
|
||||
* `path` {string} Directory where the report is written.
|
||||
**Default:** the current working directory of the Node.js process.
|
||||
* {string}
|
||||
|
||||
Configures the diagnostic reporting behavior. Upon invocation, the runtime
|
||||
is reconfigured to generate reports based on `options`. Several usage examples
|
||||
are shown below.
|
||||
Filename where the report is written. If set to the empty string, the output
|
||||
filename will be comprised of a timestamp, PID, and sequence number. The default
|
||||
value is the empty string.
|
||||
|
||||
```js
|
||||
// Trigger a report on uncaught exceptions or fatal errors.
|
||||
process.report.setOptions({ events: ['exception', 'fatalerror'] });
|
||||
|
||||
// Change the default path and filename of the report.
|
||||
process.report.setOptions({ filename: 'foo.json', path: '/home' });
|
||||
|
||||
// Produce the report onto stdout, when generated. Special meaning is attached
|
||||
// to `stdout` and `stderr`. Usage of these will result in report being written
|
||||
// to the associated standard streams. URLs are not supported.
|
||||
process.report.setOptions({ filename: 'stdout' });
|
||||
console.log(`Report filename is ${process.report.filename}`);
|
||||
```
|
||||
|
||||
Signal based report generation is not supported on Windows.
|
||||
## process.report.reportOnFatalError
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Additional documentation is available in the [report documentation][].
|
||||
* {boolean}
|
||||
|
||||
If `true`, a diagnostic report is generated on fatal errors, such as out of
|
||||
memory errors or failed C++ assertions.
|
||||
|
||||
```js
|
||||
console.log(`Report on fatal error: ${process.report.reportOnFatalError}`);
|
||||
```
|
||||
|
||||
## process.report.reportOnSignal
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {boolean}
|
||||
|
||||
If `true`, a diagnostic report is generated when the process receives the
|
||||
signal specified by `process.report.signal`.
|
||||
|
||||
```js
|
||||
console.log(`Report on signal: ${process.report.reportOnSignal}`);
|
||||
```
|
||||
|
||||
## process.report.reportOnUncaughtException
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {boolean}
|
||||
|
||||
If `true`, a diagnostic report is generated on uncaught exception.
|
||||
|
||||
```js
|
||||
console.log(`Report on exception: ${process.report.reportOnUncaughtException}`);
|
||||
```
|
||||
|
||||
## process.report.signal
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {string}
|
||||
|
||||
The signal used to trigger the creation of a diagnostic report. Defaults to
|
||||
`SIGUSR2`.
|
||||
|
||||
```js
|
||||
console.log(`Report signal: ${process.report.signal}`);
|
||||
```
|
||||
|
||||
### process.report.triggerReport([filename][, err])
|
||||
<!-- YAML
|
||||
@ -1732,7 +1779,7 @@ added: v11.8.0
|
||||
|
||||
* `filename` {string} Name of the file where the report is written. This
|
||||
should be a relative path, that will be appended to the directory specified in
|
||||
`process.report.setOptions`, or the current working directory of the Node.js
|
||||
`process.report.directory`, or the current working directory of the Node.js
|
||||
process, if unspecified.
|
||||
* `err` {Error} A custom error used for reporting the JavaScript stack.
|
||||
|
||||
|
@ -484,21 +484,17 @@ times for the same Node.js process.
|
||||
|
||||
## Configuration
|
||||
|
||||
Additional runtime configuration that influences the report generation
|
||||
constraints are available using `setOptions()` API.
|
||||
Additional runtime configuration of report generation is available via
|
||||
the following properties of `process.report`:
|
||||
|
||||
```js
|
||||
process.report.setOptions({
|
||||
events: ['exception', 'fatalerror', 'signal'],
|
||||
signal: 'SIGUSR2',
|
||||
filename: 'myreport.json',
|
||||
path: '/home/nodeuser'
|
||||
});
|
||||
```
|
||||
`reportOnFatalError` triggers diagnostic reporting on fatal errors when `true`.
|
||||
Defaults to `false`.
|
||||
|
||||
The `events` array contains one or more of the report triggering options.
|
||||
The only valid entries are `'exception'`, `'fatalerror'` and `'signal'`.
|
||||
By default, a report is not produced on any of these events.
|
||||
`reportOnSignal` triggers diagnostic reporting on signal when `true`. This is
|
||||
not supported on Windows. Defaults to `false`.
|
||||
|
||||
`reportOnUncaughtException` triggers diagnostic reporting on uncaught exception
|
||||
when `true`. Defaults to `false`.
|
||||
|
||||
`signal` specifies the POSIX signal identifier that will be used
|
||||
to intercept external triggers for report generation. Defaults to
|
||||
@ -507,24 +503,30 @@ to intercept external triggers for report generation. Defaults to
|
||||
`filename` specifies the name of the output file in the file system.
|
||||
Special meaning is attached to `stdout` and `stderr`. Usage of these
|
||||
will result in report being written to the associated standard streams.
|
||||
In such cases when standard streams are used, value in `'path'` is ignored.
|
||||
In cases where standard streams are used, the value in `'directory'` is ignored.
|
||||
URLs are not supported. Defaults to a composite filename that contains
|
||||
timestamp, PID and sequence number.
|
||||
|
||||
`path` specifies the filesystem directory where the report will be written to.
|
||||
`directory` specifies the filesystem directory where the report will be written.
|
||||
URLs are not supported. Defaults to the current working directory of the
|
||||
Node.js process.
|
||||
|
||||
```js
|
||||
// Trigger report only on uncaught exceptions.
|
||||
process.report.setOptions({ events: ['exception'] });
|
||||
process.report.reportOnFatalError = false;
|
||||
process.report.reportOnSignal = false;
|
||||
process.report.reportOnUncaughtException = true;
|
||||
|
||||
// Trigger report for both internal errors as well as external signal.
|
||||
process.report.setOptions({ events: ['fatalerror', 'signal'] });
|
||||
process.report.reportOnFatalError = true;
|
||||
process.report.reportOnSignal = true;
|
||||
process.report.reportOnUncaughtException = false;
|
||||
|
||||
// Change the default signal to `SIGQUIT` and enable it.
|
||||
process.report.setOptions(
|
||||
{ events: ['signal'], signal: 'SIGQUIT' });
|
||||
process.report.reportOnFatalError = false;
|
||||
process.report.reportOnUncaughtException = false;
|
||||
process.report.reportOnSignal = true;
|
||||
process.report.signal = 'SIGQUIT';
|
||||
```
|
||||
|
||||
Configuration on module initialization is also available via
|
||||
|
@ -40,14 +40,8 @@ function initializeReport() {
|
||||
if (!getOptionValue('--experimental-report')) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
config,
|
||||
report,
|
||||
syncConfig
|
||||
} = require('internal/process/report');
|
||||
const { report } = require('internal/process/report');
|
||||
process.report = report;
|
||||
// Download the CLI / ENV config into JS land.
|
||||
syncConfig(config, false);
|
||||
}
|
||||
|
||||
function setupSignalHandlers() {
|
||||
@ -68,13 +62,10 @@ function initializeReportSignalHandlers() {
|
||||
if (!getOptionValue('--experimental-report')) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
config,
|
||||
handleSignal
|
||||
} = require('internal/process/report');
|
||||
if (config.events.includes('signal')) {
|
||||
process.on(config.signal, handleSignal);
|
||||
}
|
||||
|
||||
const { addSignalHandler } = require('internal/process/report');
|
||||
|
||||
addSignalHandler();
|
||||
}
|
||||
|
||||
function setupTraceCategoryState() {
|
||||
|
@ -109,18 +109,11 @@ function createFatalException() {
|
||||
if (er == null || er.domain == null) {
|
||||
try {
|
||||
const report = internalBinding('report');
|
||||
if (report != null &&
|
||||
require('internal/options')
|
||||
.getOptionValue('--experimental-report')) {
|
||||
const config = {};
|
||||
report.syncConfig(config, false);
|
||||
if (Array.isArray(config.events) &&
|
||||
config.events.includes('exception')) {
|
||||
report.triggerReport(er ? er.message : 'Exception',
|
||||
'Exception',
|
||||
null,
|
||||
er ? er.stack : undefined);
|
||||
}
|
||||
if (report != null && report.shouldReportOnUncaughtException()) {
|
||||
report.triggerReport(er ? er.message : 'Exception',
|
||||
'Exception',
|
||||
null,
|
||||
er ? er.stack : undefined);
|
||||
}
|
||||
} catch {} // Ignore the exception. Diagnostic reporting is unavailable.
|
||||
}
|
||||
|
@ -7,72 +7,9 @@ const {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_SYNTHETIC
|
||||
} = require('internal/errors').codes;
|
||||
|
||||
// If report is enabled, extract the binding and
|
||||
// wrap the APIs with thin layers, with some error checks.
|
||||
// user options can come in from CLI / ENV / API.
|
||||
// CLI and ENV is intercepted in C++ and the API call here (JS).
|
||||
// So sync up with both sides as appropriate - initially from
|
||||
// C++ to JS and from JS to C++ whenever the API is called.
|
||||
// Some events are controlled purely from JS (signal | exception)
|
||||
// and some from C++ (fatalerror) so this sync-up is essential for
|
||||
// correct behavior and alignment with the supplied tunables.
|
||||
const { validateString } = require('internal/validators');
|
||||
const nr = internalBinding('report');
|
||||
|
||||
// Keep it un-exposed; lest programs play with it
|
||||
// leaving us with a lot of unwanted sanity checks.
|
||||
let config = {
|
||||
events: [],
|
||||
signal: 'SIGUSR2',
|
||||
filename: '',
|
||||
path: ''
|
||||
};
|
||||
const report = {
|
||||
setOptions(options) {
|
||||
emitExperimentalWarning('report');
|
||||
const previousConfig = config;
|
||||
const newConfig = {};
|
||||
|
||||
if (options === null || typeof options !== 'object')
|
||||
options = {};
|
||||
|
||||
if (Array.isArray(options.events))
|
||||
newConfig.events = options.events.slice();
|
||||
else if (options.events === undefined)
|
||||
newConfig.events = [];
|
||||
else
|
||||
throw new ERR_INVALID_ARG_TYPE('events', 'Array', options.events);
|
||||
|
||||
if (typeof options.filename === 'string')
|
||||
newConfig.filename = options.filename;
|
||||
else if (options.filename === undefined)
|
||||
newConfig.filename = '';
|
||||
else
|
||||
throw new ERR_INVALID_ARG_TYPE('filename', 'string', options.filename);
|
||||
|
||||
if (typeof options.path === 'string')
|
||||
newConfig.path = options.path;
|
||||
else if (options.path === undefined)
|
||||
newConfig.path = '';
|
||||
else
|
||||
throw new ERR_INVALID_ARG_TYPE('path', 'string', options.path);
|
||||
|
||||
if (typeof options.signal === 'string')
|
||||
newConfig.signal = convertToValidSignal(options.signal);
|
||||
else if (options.signal === undefined)
|
||||
newConfig.signal = 'SIGUSR2';
|
||||
else
|
||||
throw new ERR_INVALID_ARG_TYPE('signal', 'string', options.signal);
|
||||
|
||||
if (previousConfig.signal)
|
||||
process.removeListener(previousConfig.signal, handleSignal);
|
||||
|
||||
if (newConfig.events.includes('signal'))
|
||||
process.on(newConfig.signal, handleSignal);
|
||||
|
||||
config = newConfig;
|
||||
nr.syncConfig(config, true);
|
||||
},
|
||||
triggerReport(file, err) {
|
||||
emitExperimentalWarning('report');
|
||||
|
||||
@ -98,18 +35,98 @@ const report = {
|
||||
throw new ERR_INVALID_ARG_TYPE('err', 'Object', err);
|
||||
|
||||
return nr.getReport(err.stack);
|
||||
},
|
||||
get directory() {
|
||||
emitExperimentalWarning('report');
|
||||
return nr.getDirectory();
|
||||
},
|
||||
set directory(dir) {
|
||||
emitExperimentalWarning('report');
|
||||
validateString(dir, 'directory');
|
||||
return nr.setDirectory(dir);
|
||||
},
|
||||
get filename() {
|
||||
emitExperimentalWarning('report');
|
||||
return nr.getFilename();
|
||||
},
|
||||
set filename(name) {
|
||||
emitExperimentalWarning('report');
|
||||
validateString(name, 'filename');
|
||||
return nr.setFilename(name);
|
||||
},
|
||||
get signal() {
|
||||
emitExperimentalWarning('report');
|
||||
return nr.getSignal();
|
||||
},
|
||||
set signal(sig) {
|
||||
emitExperimentalWarning('report');
|
||||
validateString(sig, 'signal');
|
||||
convertToValidSignal(sig); // Validate that the signal is recognized.
|
||||
removeSignalHandler();
|
||||
addSignalHandler(sig);
|
||||
return nr.setSignal(sig);
|
||||
},
|
||||
get reportOnFatalError() {
|
||||
emitExperimentalWarning('report');
|
||||
return nr.shouldReportOnFatalError();
|
||||
},
|
||||
set reportOnFatalError(trigger) {
|
||||
emitExperimentalWarning('report');
|
||||
|
||||
if (typeof trigger !== 'boolean')
|
||||
throw new ERR_INVALID_ARG_TYPE('trigger', 'boolean', trigger);
|
||||
|
||||
return nr.setReportOnFatalError(trigger);
|
||||
},
|
||||
get reportOnSignal() {
|
||||
emitExperimentalWarning('report');
|
||||
return nr.shouldReportOnSignal();
|
||||
},
|
||||
set reportOnSignal(trigger) {
|
||||
emitExperimentalWarning('report');
|
||||
|
||||
if (typeof trigger !== 'boolean')
|
||||
throw new ERR_INVALID_ARG_TYPE('trigger', 'boolean', trigger);
|
||||
|
||||
nr.setReportOnSignal(trigger);
|
||||
removeSignalHandler();
|
||||
addSignalHandler();
|
||||
},
|
||||
get reportOnUncaughtException() {
|
||||
emitExperimentalWarning('report');
|
||||
return nr.shouldReportOnUncaughtException();
|
||||
},
|
||||
set reportOnUncaughtException(trigger) {
|
||||
emitExperimentalWarning('report');
|
||||
|
||||
if (typeof trigger !== 'boolean')
|
||||
throw new ERR_INVALID_ARG_TYPE('trigger', 'boolean', trigger);
|
||||
|
||||
return nr.setReportOnUncaughtException(trigger);
|
||||
}
|
||||
};
|
||||
|
||||
function handleSignal(signo) {
|
||||
if (typeof signo !== 'string')
|
||||
signo = config.signal;
|
||||
nr.triggerReport(signo, 'Signal', null, '');
|
||||
function addSignalHandler(sig) {
|
||||
if (nr.shouldReportOnSignal()) {
|
||||
if (typeof sig !== 'string')
|
||||
sig = nr.getSignal();
|
||||
|
||||
process.on(sig, signalHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function removeSignalHandler() {
|
||||
const sig = nr.getSignal();
|
||||
|
||||
if (sig)
|
||||
process.removeListener(sig, signalHandler);
|
||||
}
|
||||
|
||||
function signalHandler(sig) {
|
||||
nr.triggerReport(sig, 'Signal', null, '');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
config,
|
||||
handleSignal,
|
||||
report,
|
||||
syncConfig: nr.syncConfig
|
||||
addSignalHandler,
|
||||
report
|
||||
};
|
||||
|
@ -57,8 +57,14 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors) {
|
||||
void PerIsolateOptions::CheckOptions(std::vector<std::string>* errors) {
|
||||
per_env->CheckOptions(errors);
|
||||
#ifdef NODE_REPORT
|
||||
if (per_env->experimental_report)
|
||||
if (per_env->experimental_report) {
|
||||
// Assign the report_signal default value here. Once the
|
||||
// --experimental-report flag is dropped, move this initialization to
|
||||
// node_options.h, where report_signal is declared.
|
||||
if (report_signal.empty())
|
||||
report_signal = "SIGUSR2";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!report_directory.empty()) {
|
||||
errors->push_back("--diagnostic-report-directory option is valid only when "
|
||||
|
@ -18,10 +18,7 @@
|
||||
|
||||
namespace report {
|
||||
using node::Environment;
|
||||
using node::FIXED_ONE_BYTE_STRING;
|
||||
using node::PerIsolateOptions;
|
||||
using node::Utf8Value;
|
||||
using v8::Array;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
using v8::Function;
|
||||
@ -31,7 +28,6 @@ using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::Object;
|
||||
using v8::String;
|
||||
using v8::V8;
|
||||
using v8::Value;
|
||||
|
||||
// External JavaScript API for triggering a report
|
||||
@ -75,129 +71,89 @@ void GetReport(const FunctionCallbackInfo<Value>& info) {
|
||||
.ToLocalChecked());
|
||||
}
|
||||
|
||||
// A method to sync up data elements in the JS land with its
|
||||
// corresponding elements in the C++ world. Required because
|
||||
// (i) the tunables are first intercepted through the CLI but
|
||||
// later modified via APIs. (ii) the report generation events
|
||||
// are controlled partly from C++ and partly from JS.
|
||||
void SyncConfig(const FunctionCallbackInfo<Value>& info) {
|
||||
static void GetDirectory(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
Local<Context> context = env->context();
|
||||
std::shared_ptr<PerIsolateOptions> options = env->isolate_data()->options();
|
||||
std::string directory = env->isolate_data()->options()->report_directory;
|
||||
auto result = String::NewFromUtf8(env->isolate(),
|
||||
directory.c_str(),
|
||||
v8::NewStringType::kNormal);
|
||||
info.GetReturnValue().Set(result.ToLocalChecked());
|
||||
}
|
||||
|
||||
CHECK_EQ(info.Length(), 2);
|
||||
Local<Object> obj;
|
||||
if (!info[0]->ToObject(context).ToLocal(&obj)) return;
|
||||
bool sync = info[1].As<Boolean>()->Value();
|
||||
static void SetDirectory(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
CHECK(info[0]->IsString());
|
||||
Utf8Value dir(env->isolate(), info[0].As<String>());
|
||||
env->isolate_data()->options()->report_directory = *dir;
|
||||
}
|
||||
|
||||
// Events array
|
||||
Local<String> eventskey = FIXED_ONE_BYTE_STRING(env->isolate(), "events");
|
||||
Local<Value> events_unchecked;
|
||||
if (!obj->Get(context, eventskey).ToLocal(&events_unchecked)) return;
|
||||
Local<Array> events;
|
||||
if (events_unchecked->IsUndefined() || events_unchecked->IsNull()) {
|
||||
events_unchecked = Array::New(env->isolate(), 0);
|
||||
if (obj->Set(context, eventskey, events_unchecked).IsNothing()) return;
|
||||
}
|
||||
events = events_unchecked.As<Array>();
|
||||
static void GetFilename(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
std::string filename = env->isolate_data()->options()->report_filename;
|
||||
auto result = String::NewFromUtf8(env->isolate(),
|
||||
filename.c_str(),
|
||||
v8::NewStringType::kNormal);
|
||||
info.GetReturnValue().Set(result.ToLocalChecked());
|
||||
}
|
||||
|
||||
// Signal
|
||||
Local<String> signalkey = env->signal_string();
|
||||
Local<Value> signal_unchecked;
|
||||
if (!obj->Get(context, signalkey).ToLocal(&signal_unchecked)) return;
|
||||
Local<String> signal;
|
||||
if (signal_unchecked->IsUndefined() || signal_unchecked->IsNull())
|
||||
signal_unchecked = signalkey;
|
||||
signal = signal_unchecked.As<String>();
|
||||
static void SetFilename(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
CHECK(info[0]->IsString());
|
||||
Utf8Value name(env->isolate(), info[0].As<String>());
|
||||
env->isolate_data()->options()->report_filename = *name;
|
||||
}
|
||||
|
||||
Utf8Value signalstr(env->isolate(), signal);
|
||||
static void GetSignal(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
std::string signal = env->isolate_data()->options()->report_signal;
|
||||
auto result = String::NewFromUtf8(env->isolate(),
|
||||
signal.c_str(),
|
||||
v8::NewStringType::kNormal);
|
||||
info.GetReturnValue().Set(result.ToLocalChecked());
|
||||
}
|
||||
|
||||
// Report file
|
||||
Local<String> filekey = FIXED_ONE_BYTE_STRING(env->isolate(), "filename");
|
||||
Local<Value> file_unchecked;
|
||||
if (!obj->Get(context, filekey).ToLocal(&file_unchecked)) return;
|
||||
Local<String> file;
|
||||
if (file_unchecked->IsUndefined() || file_unchecked->IsNull())
|
||||
file_unchecked = filekey;
|
||||
file = file_unchecked.As<String>();
|
||||
static void SetSignal(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
CHECK(info[0]->IsString());
|
||||
Utf8Value signal(env->isolate(), info[0].As<String>());
|
||||
env->isolate_data()->options()->report_signal = *signal;
|
||||
}
|
||||
|
||||
Utf8Value filestr(env->isolate(), file);
|
||||
static void ShouldReportOnFatalError(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
info.GetReturnValue().Set(
|
||||
env->isolate_data()->options()->report_on_fatalerror);
|
||||
}
|
||||
|
||||
// Report file path
|
||||
Local<String> pathkey = FIXED_ONE_BYTE_STRING(env->isolate(), "path");
|
||||
Local<Value> path_unchecked;
|
||||
if (!obj->Get(context, pathkey).ToLocal(&path_unchecked)) return;
|
||||
Local<String> path;
|
||||
if (path_unchecked->IsUndefined() || path_unchecked->IsNull())
|
||||
path_unchecked = pathkey;
|
||||
path = path_unchecked.As<String>();
|
||||
static void SetReportOnFatalError(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
CHECK(info[0]->IsBoolean());
|
||||
env->isolate_data()->options()->report_on_fatalerror = info[0]->IsTrue();
|
||||
}
|
||||
|
||||
Utf8Value pathstr(env->isolate(), path);
|
||||
static void ShouldReportOnSignal(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
info.GetReturnValue().Set(env->isolate_data()->options()->report_on_signal);
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
static const std::string e = "exception";
|
||||
static const std::string s = "signal";
|
||||
static const std::string f = "fatalerror";
|
||||
for (uint32_t i = 0; i < events->Length(); i++) {
|
||||
Local<Value> v;
|
||||
if (!events->Get(context, i).ToLocal(&v)) return;
|
||||
Local<String> elem;
|
||||
if (!v->ToString(context).ToLocal(&elem)) return;
|
||||
String::Utf8Value buf(env->isolate(), elem);
|
||||
if (*buf == e) {
|
||||
options->report_uncaught_exception = true;
|
||||
} else if (*buf == s) {
|
||||
options->report_on_signal = true;
|
||||
} else if (*buf == f) {
|
||||
options->report_on_fatalerror = true;
|
||||
}
|
||||
}
|
||||
CHECK_NOT_NULL(*signalstr);
|
||||
options->report_signal = *signalstr;
|
||||
CHECK_NOT_NULL(*filestr);
|
||||
options->report_filename = *filestr;
|
||||
CHECK_NOT_NULL(*pathstr);
|
||||
options->report_directory = *pathstr;
|
||||
} else {
|
||||
int i = 0;
|
||||
if (options->report_uncaught_exception &&
|
||||
events
|
||||
->Set(context,
|
||||
i++,
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "exception"))
|
||||
.IsNothing())
|
||||
return;
|
||||
if (options->report_on_signal &&
|
||||
events
|
||||
->Set(context, i++, FIXED_ONE_BYTE_STRING(env->isolate(), "signal"))
|
||||
.IsNothing())
|
||||
return;
|
||||
if (options->report_on_fatalerror &&
|
||||
events
|
||||
->Set(
|
||||
context, i, FIXED_ONE_BYTE_STRING(env->isolate(), "fatalerror"))
|
||||
.IsNothing())
|
||||
return;
|
||||
static void SetReportOnSignal(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
CHECK(info[0]->IsBoolean());
|
||||
env->isolate_data()->options()->report_on_signal = info[0]->IsTrue();
|
||||
}
|
||||
|
||||
Local<Value> signal_value;
|
||||
Local<Value> file_value;
|
||||
Local<Value> path_value;
|
||||
std::string signal = options->report_signal;
|
||||
if (signal.empty()) signal = "SIGUSR2";
|
||||
if (!node::ToV8Value(context, signal).ToLocal(&signal_value))
|
||||
return;
|
||||
if (!obj->Set(context, signalkey, signal_value).FromJust()) return;
|
||||
static void ShouldReportOnUncaughtException(
|
||||
const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
info.GetReturnValue().Set(
|
||||
env->isolate_data()->options()->report_uncaught_exception);
|
||||
}
|
||||
|
||||
if (!node::ToV8Value(context, options->report_filename)
|
||||
.ToLocal(&file_value))
|
||||
return;
|
||||
if (!obj->Set(context, filekey, file_value).FromJust()) return;
|
||||
|
||||
if (!node::ToV8Value(context, options->report_directory)
|
||||
.ToLocal(&path_value))
|
||||
return;
|
||||
if (!obj->Set(context, pathkey, path_value).FromJust()) return;
|
||||
}
|
||||
static void SetReportOnUncaughtException(
|
||||
const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
CHECK(info[0]->IsBoolean());
|
||||
env->isolate_data()->options()->report_uncaught_exception = info[0]->IsTrue();
|
||||
}
|
||||
|
||||
static void Initialize(Local<Object> exports,
|
||||
@ -207,7 +163,20 @@ static void Initialize(Local<Object> exports,
|
||||
|
||||
env->SetMethod(exports, "triggerReport", TriggerReport);
|
||||
env->SetMethod(exports, "getReport", GetReport);
|
||||
env->SetMethod(exports, "syncConfig", SyncConfig);
|
||||
env->SetMethod(exports, "getDirectory", GetDirectory);
|
||||
env->SetMethod(exports, "setDirectory", SetDirectory);
|
||||
env->SetMethod(exports, "getFilename", GetFilename);
|
||||
env->SetMethod(exports, "setFilename", SetFilename);
|
||||
env->SetMethod(exports, "getSignal", GetSignal);
|
||||
env->SetMethod(exports, "setSignal", SetSignal);
|
||||
env->SetMethod(exports, "shouldReportOnFatalError", ShouldReportOnFatalError);
|
||||
env->SetMethod(exports, "setReportOnFatalError", SetReportOnFatalError);
|
||||
env->SetMethod(exports, "shouldReportOnSignal", ShouldReportOnSignal);
|
||||
env->SetMethod(exports, "setReportOnSignal", SetReportOnSignal);
|
||||
env->SetMethod(exports, "shouldReportOnUncaughtException",
|
||||
ShouldReportOnUncaughtException);
|
||||
env->SetMethod(exports, "setReportOnUncaughtException",
|
||||
SetReportOnUncaughtException);
|
||||
}
|
||||
|
||||
} // namespace report
|
||||
|
87
test/report/test-report-config.js
Normal file
87
test/report/test-report-config.js
Normal file
@ -0,0 +1,87 @@
|
||||
// Flags: --experimental-report --diagnostic-report-on-fatalerror --diagnostic-report-on-signal --diagnostic-report-uncaught-exception
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
common.skipIfReportDisabled();
|
||||
const assert = require('assert');
|
||||
|
||||
common.expectWarning('ExperimentalWarning',
|
||||
'report is an experimental feature. This feature could ' +
|
||||
'change at any time');
|
||||
|
||||
// Verify that process.report.directory behaves properly.
|
||||
assert.strictEqual(process.report.directory, '');
|
||||
process.report.directory = __dirname;
|
||||
assert.strictEqual(process.report.directory, __dirname);
|
||||
common.expectsError(() => {
|
||||
process.report.directory = {};
|
||||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
assert.strictEqual(process.report.directory, __dirname);
|
||||
|
||||
// Verify that process.report.filename behaves properly.
|
||||
assert.strictEqual(process.report.filename, '');
|
||||
process.report.filename = 'test-report.json';
|
||||
assert.strictEqual(process.report.filename, 'test-report.json');
|
||||
common.expectsError(() => {
|
||||
process.report.filename = {};
|
||||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
assert.strictEqual(process.report.filename, 'test-report.json');
|
||||
|
||||
// Verify that process.report.reportOnFatalError behaves properly.
|
||||
assert.strictEqual(process.report.reportOnFatalError, true);
|
||||
process.report.reportOnFatalError = false;
|
||||
assert.strictEqual(process.report.reportOnFatalError, false);
|
||||
process.report.reportOnFatalError = true;
|
||||
assert.strictEqual(process.report.reportOnFatalError, true);
|
||||
common.expectsError(() => {
|
||||
process.report.reportOnFatalError = {};
|
||||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
assert.strictEqual(process.report.reportOnFatalError, true);
|
||||
|
||||
|
||||
// Verify that process.report.reportOnUncaughtException behaves properly.
|
||||
assert.strictEqual(process.report.reportOnUncaughtException, true);
|
||||
process.report.reportOnUncaughtException = false;
|
||||
assert.strictEqual(process.report.reportOnUncaughtException, false);
|
||||
process.report.reportOnUncaughtException = true;
|
||||
assert.strictEqual(process.report.reportOnUncaughtException, true);
|
||||
common.expectsError(() => {
|
||||
process.report.reportOnUncaughtException = {};
|
||||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
assert.strictEqual(process.report.reportOnUncaughtException, true);
|
||||
|
||||
// Verify that process.report.reportOnSignal behaves properly.
|
||||
assert.strictEqual(process.report.reportOnSignal, true);
|
||||
process.report.reportOnSignal = false;
|
||||
assert.strictEqual(process.report.reportOnSignal, false);
|
||||
process.report.reportOnSignal = true;
|
||||
assert.strictEqual(process.report.reportOnSignal, true);
|
||||
common.expectsError(() => {
|
||||
process.report.reportOnSignal = {};
|
||||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
assert.strictEqual(process.report.reportOnSignal, true);
|
||||
|
||||
if (!common.isWindows) {
|
||||
// Verify that process.report.signal behaves properly.
|
||||
assert.strictEqual(process.report.signal, 'SIGUSR2');
|
||||
common.expectsError(() => {
|
||||
process.report.signal = {};
|
||||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
common.expectsError(() => {
|
||||
process.report.signal = 'foo';
|
||||
}, { code: 'ERR_UNKNOWN_SIGNAL' });
|
||||
assert.strictEqual(process.report.signal, 'SIGUSR2');
|
||||
process.report.signal = 'SIGUSR1';
|
||||
assert.strictEqual(process.report.signal, 'SIGUSR1');
|
||||
|
||||
// Verify that the interaction between reportOnSignal and signal is correct.
|
||||
process.report.signal = 'SIGUSR2';
|
||||
process.report.reportOnSignal = false;
|
||||
assert.strictEqual(process.listenerCount('SIGUSR2'), 0);
|
||||
process.report.reportOnSignal = true;
|
||||
assert.strictEqual(process.listenerCount('SIGUSR2'), 1);
|
||||
process.report.signal = 'SIGUSR1';
|
||||
assert.strictEqual(process.listenerCount('SIGUSR2'), 0);
|
||||
assert.strictEqual(process.listenerCount('SIGUSR1'), 1);
|
||||
process.report.reportOnSignal = false;
|
||||
assert.strictEqual(process.listenerCount('SIGUSR1'), 0);
|
||||
}
|
@ -64,6 +64,7 @@ if (process.argv[2] === 'child') {
|
||||
});
|
||||
child.on('exit', common.mustCall((code, signal) => {
|
||||
console.log('child exited');
|
||||
console.log(stderr);
|
||||
const report_msg = 'No reports found';
|
||||
const process_msg = 'Process exited unexpectedly';
|
||||
const signal_msg = 'Process exited with unexpected signal';
|
||||
|
@ -14,7 +14,7 @@ common.expectWarning('ExperimentalWarning',
|
||||
'report is an experimental feature. This feature could ' +
|
||||
'change at any time');
|
||||
tmpdir.refresh();
|
||||
process.report.setOptions({ path: tmpdir.path });
|
||||
process.report.directory = tmpdir.path;
|
||||
|
||||
function validate() {
|
||||
const reports = helper.findReports(process.pid, tmpdir.path);
|
||||
@ -59,11 +59,11 @@ function validate() {
|
||||
|
||||
{
|
||||
// Test with a filename option.
|
||||
const filename = path.join(tmpdir.path, 'custom-name-3.json');
|
||||
process.report.setOptions({ filename });
|
||||
process.report.filename = 'custom-name-3.json';
|
||||
const file = process.report.triggerReport();
|
||||
assert.strictEqual(helper.findReports(process.pid, tmpdir.path).length, 0);
|
||||
assert.strictEqual(file, filename);
|
||||
const filename = path.join(process.report.directory, 'custom-name-3.json');
|
||||
assert.strictEqual(file, process.report.filename);
|
||||
helper.validate(filename);
|
||||
fs.unlinkSync(filename);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ common.expectWarning('ExperimentalWarning',
|
||||
'report is an experimental feature. This feature could ' +
|
||||
'change at any time');
|
||||
tmpdir.refresh();
|
||||
process.report.setOptions({ path: tmpdir.path });
|
||||
process.report.directory = tmpdir.path;
|
||||
|
||||
process.on('uncaughtException', common.mustCall((err) => {
|
||||
assert.strictEqual(err, error);
|
||||
|
Loading…
x
Reference in New Issue
Block a user