mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
src,cli: support compact (one-line) JSON reports
Multi-line JSON is more human readable, but harder for log aggregators to consume, they usually require a log message per line, particularly for JSON. Compact output will be consumable by aggregators such as EFK (Elastic Search-Fluentd-Kibana), LogDNA, DataDog, etc. PR-URL: https://github.com/nodejs/node/pull/32254 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
5adaf1092a
commit
b5b7bf5ea4
|
|
@ -592,6 +592,15 @@ file will be created if it does not exist, and will be appended to if it does.
|
|||
If an error occurs while attempting to write the warning to the file, the
|
||||
warning will be written to stderr instead.
|
||||
|
||||
### `--report-compact`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Write reports in a compact format, single-line JSON, more easily consumable
|
||||
by log processing systems than the default multi-line format designed for
|
||||
human consumption.
|
||||
|
||||
### `--report-directory=directory`
|
||||
<!-- YAML
|
||||
added: v11.8.0
|
||||
|
|
@ -1136,6 +1145,7 @@ Node.js options that are allowed are:
|
|||
* `--preserve-symlinks`
|
||||
* `--prof-process`
|
||||
* `--redirect-warnings`
|
||||
* `--report-compact`
|
||||
* `--report-directory`
|
||||
* `--report-filename`
|
||||
* `--report-on-fatalerror`
|
||||
|
|
|
|||
|
|
@ -1767,6 +1767,21 @@ changes:
|
|||
reports for the current process. Additional documentation is available in the
|
||||
[report documentation][].
|
||||
|
||||
### `process.report.compact`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* {boolean}
|
||||
|
||||
Write reports in a compact format, single-line JSON, more easily consumable
|
||||
by log processing systems than the default multi-line format designed for
|
||||
human consumption.
|
||||
|
||||
```js
|
||||
console.log(`Reports are compact? ${process.report.compact}`);
|
||||
```
|
||||
|
||||
### `process.report.directory`
|
||||
<!-- YAML
|
||||
added: v11.12.0
|
||||
|
|
|
|||
|
|
@ -419,6 +419,10 @@ that leads to termination of the application. Useful to inspect various
|
|||
diagnostic data elements such as heap, stack, event loop state, resource
|
||||
consumption etc. to reason about the fatal error.
|
||||
|
||||
* `--report-compact` Write reports in a compact format, single-line JSON, more
|
||||
easily consumable by log processing systems than the default multi-line format
|
||||
designed for human consumption.
|
||||
|
||||
* `--report-directory` Location at which the report will be
|
||||
generated.
|
||||
|
||||
|
|
|
|||
|
|
@ -281,6 +281,11 @@ Write process warnings to the given
|
|||
.Ar file
|
||||
instead of printing to stderr.
|
||||
.
|
||||
.It Fl -report-compact
|
||||
Write
|
||||
.Sy diagnostic reports
|
||||
in a compact format, single-line JSON.
|
||||
.
|
||||
.It Fl -report-directory
|
||||
Location at which the
|
||||
.Sy diagnostic report
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ const {
|
|||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_SYNTHETIC
|
||||
} = require('internal/errors').codes;
|
||||
const { validateSignalName, validateString } = require('internal/validators');
|
||||
const {
|
||||
validateSignalName,
|
||||
validateString,
|
||||
validateBoolean,
|
||||
} = require('internal/validators');
|
||||
const nr = internalBinding('report');
|
||||
const {
|
||||
JSONParse,
|
||||
|
|
@ -45,6 +49,13 @@ const report = {
|
|||
validateString(name, 'filename');
|
||||
nr.setFilename(name);
|
||||
},
|
||||
get compact() {
|
||||
return nr.getCompact();
|
||||
},
|
||||
set compact(b) {
|
||||
validateBoolean(b, 'compact');
|
||||
nr.setCompact(b);
|
||||
},
|
||||
get signal() {
|
||||
return nr.getSignal();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -577,6 +577,10 @@ PerIsolateOptionsParser::PerIsolateOptionsParser(
|
|||
"generate diagnostic report on uncaught exceptions",
|
||||
&PerIsolateOptions::report_uncaught_exception,
|
||||
kAllowedInEnvironment);
|
||||
AddOption("--report-compact",
|
||||
"output compact single-line JSON",
|
||||
&PerIsolateOptions::report_compact,
|
||||
kAllowedInEnvironment);
|
||||
AddOption("--report-on-signal",
|
||||
"generate diagnostic report upon receiving signals",
|
||||
&PerIsolateOptions::report_on_signal,
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ class PerIsolateOptions : public Options {
|
|||
bool report_uncaught_exception = false;
|
||||
bool report_on_signal = false;
|
||||
bool report_on_fatalerror = false;
|
||||
bool report_compact = false;
|
||||
std::string report_signal = "SIGUSR2";
|
||||
std::string report_filename;
|
||||
std::string report_directory;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ static void WriteNodeReport(Isolate* isolate,
|
|||
const char* trigger,
|
||||
const std::string& filename,
|
||||
std::ostream& out,
|
||||
Local<String> stackstr);
|
||||
Local<String> stackstr,
|
||||
bool compact);
|
||||
static void PrintVersionInformation(JSONWriter* writer);
|
||||
static void PrintJavaScriptStack(JSONWriter* writer,
|
||||
Isolate* isolate,
|
||||
|
|
@ -126,8 +127,9 @@ std::string TriggerNodeReport(Isolate* isolate,
|
|||
std::cerr << "\nWriting Node.js report to file: " << filename;
|
||||
}
|
||||
|
||||
bool compact = env != nullptr ? options->report_compact : true;
|
||||
WriteNodeReport(isolate, env, message, trigger, filename, *outstream,
|
||||
stackstr);
|
||||
stackstr, compact);
|
||||
|
||||
// Do not close stdout/stderr, only close files we opened.
|
||||
if (outfile.is_open()) {
|
||||
|
|
@ -145,7 +147,7 @@ void GetNodeReport(Isolate* isolate,
|
|||
const char* trigger,
|
||||
Local<String> stackstr,
|
||||
std::ostream& out) {
|
||||
WriteNodeReport(isolate, env, message, trigger, "", out, stackstr);
|
||||
WriteNodeReport(isolate, env, message, trigger, "", out, stackstr, false);
|
||||
}
|
||||
|
||||
// Internal function to coordinate and write the various
|
||||
|
|
@ -156,7 +158,8 @@ static void WriteNodeReport(Isolate* isolate,
|
|||
const char* trigger,
|
||||
const std::string& filename,
|
||||
std::ostream& out,
|
||||
Local<String> stackstr) {
|
||||
Local<String> stackstr,
|
||||
bool compact) {
|
||||
// Obtain the current time and the pid.
|
||||
TIME_TYPE tm_struct;
|
||||
DiagnosticFilename::LocalTime(&tm_struct);
|
||||
|
|
@ -169,7 +172,7 @@ static void WriteNodeReport(Isolate* isolate,
|
|||
// File stream opened OK, now start printing the report content:
|
||||
// the title and header information (event, filename, timestamp and pid)
|
||||
|
||||
JSONWriter writer(out);
|
||||
JSONWriter writer(out, compact);
|
||||
writer.json_start();
|
||||
writer.json_objectstart("header");
|
||||
writer.json_keyvalue("reportVersion", NODE_REPORT_VERSION);
|
||||
|
|
|
|||
|
|
@ -65,17 +65,29 @@ extern double prog_start_time;
|
|||
// JSON compiler definitions.
|
||||
class JSONWriter {
|
||||
public:
|
||||
explicit JSONWriter(std::ostream& out) : out_(out) {}
|
||||
JSONWriter(std::ostream& out, bool compact)
|
||||
: out_(out), compact_(compact) {}
|
||||
|
||||
private:
|
||||
inline void indent() { indent_ += 2; }
|
||||
inline void deindent() { indent_ -= 2; }
|
||||
inline void advance() {
|
||||
if (compact_) return;
|
||||
for (int i = 0; i < indent_; i++) out_ << ' ';
|
||||
}
|
||||
inline void write_one_space() {
|
||||
if (compact_) return;
|
||||
out_ << ' ';
|
||||
}
|
||||
inline void write_new_line() {
|
||||
if (compact_) return;
|
||||
out_ << '\n';
|
||||
}
|
||||
|
||||
public:
|
||||
inline void json_start() {
|
||||
if (state_ == kAfterValue) out_ << ',';
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
advance();
|
||||
out_ << '{';
|
||||
indent();
|
||||
|
|
@ -83,7 +95,7 @@ class JSONWriter {
|
|||
}
|
||||
|
||||
inline void json_end() {
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
deindent();
|
||||
advance();
|
||||
out_ << '}';
|
||||
|
|
@ -92,10 +104,12 @@ class JSONWriter {
|
|||
template <typename T>
|
||||
inline void json_objectstart(T key) {
|
||||
if (state_ == kAfterValue) out_ << ',';
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
advance();
|
||||
write_string(key);
|
||||
out_ << ": {";
|
||||
out_ << ':';
|
||||
write_one_space();
|
||||
out_ << '{';
|
||||
indent();
|
||||
state_ = kObjectStart;
|
||||
}
|
||||
|
|
@ -103,23 +117,29 @@ class JSONWriter {
|
|||
template <typename T>
|
||||
inline void json_arraystart(T key) {
|
||||
if (state_ == kAfterValue) out_ << ',';
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
advance();
|
||||
write_string(key);
|
||||
out_ << ": [";
|
||||
out_ << ':';
|
||||
write_one_space();
|
||||
out_ << '[';
|
||||
indent();
|
||||
state_ = kObjectStart;
|
||||
}
|
||||
inline void json_objectend() {
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
deindent();
|
||||
advance();
|
||||
out_ << '}';
|
||||
if (indent_ == 0) {
|
||||
// Top-level object is complete, so end the line.
|
||||
out_ << '\n';
|
||||
}
|
||||
state_ = kAfterValue;
|
||||
}
|
||||
|
||||
inline void json_arrayend() {
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
deindent();
|
||||
advance();
|
||||
out_ << ']';
|
||||
|
|
@ -128,10 +148,11 @@ class JSONWriter {
|
|||
template <typename T, typename U>
|
||||
inline void json_keyvalue(const T& key, const U& value) {
|
||||
if (state_ == kAfterValue) out_ << ',';
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
advance();
|
||||
write_string(key);
|
||||
out_ << ": ";
|
||||
out_ << ':';
|
||||
write_one_space();
|
||||
write_value(value);
|
||||
state_ = kAfterValue;
|
||||
}
|
||||
|
|
@ -139,7 +160,7 @@ class JSONWriter {
|
|||
template <typename U>
|
||||
inline void json_element(const U& value) {
|
||||
if (state_ == kAfterValue) out_ << ',';
|
||||
out_ << '\n';
|
||||
write_new_line();
|
||||
advance();
|
||||
write_value(value);
|
||||
state_ = kAfterValue;
|
||||
|
|
@ -177,6 +198,7 @@ class JSONWriter {
|
|||
|
||||
enum JSONState { kObjectStart, kAfterValue };
|
||||
std::ostream& out_;
|
||||
bool compact_;
|
||||
int indent_ = 0;
|
||||
int state_ = kObjectStart;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -68,6 +68,18 @@ void GetReport(const FunctionCallbackInfo<Value>& info) {
|
|||
.ToLocalChecked());
|
||||
}
|
||||
|
||||
static void GetCompact(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
info.GetReturnValue().Set(env->isolate_data()->options()->report_compact);
|
||||
}
|
||||
|
||||
static void SetCompact(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
Isolate* isolate = env->isolate();
|
||||
bool compact = info[0]->ToBoolean(isolate)->Value();
|
||||
env->isolate_data()->options()->report_compact = compact;
|
||||
}
|
||||
|
||||
static void GetDirectory(const FunctionCallbackInfo<Value>& info) {
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
std::string directory = env->isolate_data()->options()->report_directory;
|
||||
|
|
@ -161,6 +173,8 @@ static void Initialize(Local<Object> exports,
|
|||
|
||||
env->SetMethod(exports, "writeReport", WriteReport);
|
||||
env->SetMethod(exports, "getReport", GetReport);
|
||||
env->SetMethod(exports, "getCompact", GetCompact);
|
||||
env->SetMethod(exports, "setCompact", SetCompact);
|
||||
env->SetMethod(exports, "getDirectory", GetDirectory);
|
||||
env->SetMethod(exports, "setDirectory", SetDirectory);
|
||||
env->SetMethod(exports, "getFilename", GetFilename);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,12 @@ function findReports(pid, dir) {
|
|||
}
|
||||
|
||||
function validate(filepath) {
|
||||
validateContent(JSON.parse(fs.readFileSync(filepath, 'utf8')));
|
||||
const report = fs.readFileSync(filepath, 'utf8');
|
||||
if (process.report.compact) {
|
||||
const end = report.indexOf('\n');
|
||||
assert.strictEqual(end, report.length - 1);
|
||||
}
|
||||
validateContent(JSON.parse(report));
|
||||
}
|
||||
|
||||
function validateContent(report) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Flags: --report-on-fatalerror --report-on-signal --report-uncaught-exception
|
||||
// Flags: --report-on-fatalerror --report-on-signal --report-uncaught-exception --report-compact
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
|
|
@ -55,6 +55,17 @@ assert.throws(() => {
|
|||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
assert.strictEqual(process.report.reportOnSignal, true);
|
||||
|
||||
// Verify that process.report.reportCompact behaves properly.
|
||||
assert.strictEqual(process.report.compact, true);
|
||||
process.report.compact = false;
|
||||
assert.strictEqual(process.report.compact, false);
|
||||
process.report.compact = true;
|
||||
assert.strictEqual(process.report.compact, true);
|
||||
assert.throws(() => {
|
||||
process.report.compact = {};
|
||||
}, { code: 'ERR_INVALID_ARG_TYPE' });
|
||||
assert.strictEqual(process.report.compact, true);
|
||||
|
||||
if (!common.isWindows) {
|
||||
// Verify that process.report.signal behaves properly.
|
||||
assert.strictEqual(process.report.signal, 'SIGUSR2');
|
||||
|
|
|
|||
5
test/report/test-report-uncaught-exception-compat.js
Normal file
5
test/report/test-report-uncaught-exception-compat.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Flags: --experimental-report --report-uncaught-exception --report-compact
|
||||
'use strict';
|
||||
// Test producing a compact report on uncaught exception.
|
||||
require('../common');
|
||||
require('./test-report-uncaught-exception.js');
|
||||
Loading…
Reference in New Issue
Block a user