Skip to content

Commit 9760c6c

Browse files
himself65codebytere
authored andcommitted
src: add errorProperties on process.report
PR-URL: #28426 Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Rich Trott <[email protected]>
1 parent 0c32920 commit 9760c6c

9 files changed

+137
-38
lines changed

lib/internal/process/execution.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ function createOnGlobalUncaughtException() {
157157
report.writeReport(er ? er.message : 'Exception',
158158
'Exception',
159159
null,
160-
er ? er.stack : undefined);
160+
er ? er : {});
161161
}
162162
} catch {} // Ignore the exception. Diagnostic reporting is unavailable.
163163
}

lib/internal/process/report.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ const report = {
2525
throw new ERR_INVALID_ARG_TYPE('err', 'Object', err);
2626
}
2727

28-
return nr.writeReport('JavaScript API', 'API', file, err.stack);
28+
return nr.writeReport('JavaScript API', 'API', file, err);
2929
},
3030
getReport(err) {
3131
if (err === undefined)
3232
err = new ERR_SYNTHETIC();
3333
else if (err === null || typeof err !== 'object')
3434
throw new ERR_INVALID_ARG_TYPE('err', 'Object', err);
3535

36-
return JSONParse(nr.getReport(err.stack));
36+
return JSONParse(nr.getReport(err));
3737
},
3838
get directory() {
3939
return nr.getDirectory();

src/node_errors.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ void OnFatalError(const char* location, const char* message) {
418418

419419
if (report_on_fatalerror) {
420420
report::TriggerNodeReport(
421-
isolate, env, message, "FatalError", "", Local<String>());
421+
isolate, env, message, "FatalError", "", Local<Object>());
422422
}
423423

424424
fflush(stderr);

src/node_report.cc

+72-20
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,18 @@ using node::Mutex;
3737
using node::NativeSymbolDebuggingContext;
3838
using node::TIME_TYPE;
3939
using node::worker::Worker;
40+
using v8::Array;
41+
using v8::Context;
4042
using v8::HeapSpaceStatistics;
4143
using v8::HeapStatistics;
4244
using v8::Isolate;
4345
using v8::Local;
46+
using v8::Number;
47+
using v8::Object;
48+
using v8::StackTrace;
4449
using v8::String;
50+
using v8::TryCatch;
51+
using v8::Value;
4552
using v8::V8;
4653

4754
namespace per_process = node::per_process;
@@ -53,13 +60,16 @@ static void WriteNodeReport(Isolate* isolate,
5360
const char* trigger,
5461
const std::string& filename,
5562
std::ostream& out,
56-
Local<String> stackstr,
63+
Local<Object> error,
5764
bool compact);
5865
static void PrintVersionInformation(JSONWriter* writer);
59-
static void PrintJavaScriptStack(JSONWriter* writer,
60-
Isolate* isolate,
61-
Local<String> stackstr,
62-
const char* trigger);
66+
static void PrintJavaScriptErrorStack(JSONWriter* writer,
67+
Isolate* isolate,
68+
Local<Object> error,
69+
const char* trigger);
70+
static void PrintJavaScriptErrorProperties(JSONWriter* writer,
71+
Isolate* isolate,
72+
Local<Object> error);
6373
static void PrintNativeStack(JSONWriter* writer);
6474
static void PrintResourceUsage(JSONWriter* writer);
6575
static void PrintGCStatistics(JSONWriter* writer, Isolate* isolate);
@@ -76,7 +86,7 @@ std::string TriggerNodeReport(Isolate* isolate,
7686
const char* message,
7787
const char* trigger,
7888
const std::string& name,
79-
Local<String> stackstr) {
89+
Local<Object> error) {
8090
std::string filename;
8191

8292
// Determine the required report filename. In order of priority:
@@ -142,7 +152,7 @@ std::string TriggerNodeReport(Isolate* isolate,
142152
compact = per_process::cli_options->report_compact;
143153
}
144154
WriteNodeReport(isolate, env, message, trigger, filename, *outstream,
145-
stackstr, compact);
155+
error, compact);
146156

147157
// Do not close stdout/stderr, only close files we opened.
148158
if (outfile.is_open()) {
@@ -161,9 +171,9 @@ void GetNodeReport(Isolate* isolate,
161171
Environment* env,
162172
const char* message,
163173
const char* trigger,
164-
Local<String> stackstr,
174+
Local<Object> error,
165175
std::ostream& out) {
166-
WriteNodeReport(isolate, env, message, trigger, "", out, stackstr, false);
176+
WriteNodeReport(isolate, env, message, trigger, "", out, error, false);
167177
}
168178

169179
// Internal function to coordinate and write the various
@@ -174,7 +184,7 @@ static void WriteNodeReport(Isolate* isolate,
174184
const char* trigger,
175185
const std::string& filename,
176186
std::ostream& out,
177-
Local<String> stackstr,
187+
Local<Object> error,
178188
bool compact) {
179189
// Obtain the current time and the pid.
180190
TIME_TYPE tm_struct;
@@ -259,8 +269,13 @@ static void WriteNodeReport(Isolate* isolate,
259269
PrintVersionInformation(&writer);
260270
writer.json_objectend();
261271

262-
// Report summary JavaScript stack backtrace
263-
PrintJavaScriptStack(&writer, isolate, stackstr, trigger);
272+
writer.json_objectstart("javascriptStack");
273+
// Report summary JavaScript error stack backtrace
274+
PrintJavaScriptErrorStack(&writer, isolate, error, trigger);
275+
276+
// Report summary JavaScript error properties backtrace
277+
PrintJavaScriptErrorProperties(&writer, isolate, error);
278+
writer.json_objectend(); // the end of 'javascriptStack'
264279

265280
// Report native stack backtrace
266281
PrintNativeStack(&writer);
@@ -301,7 +316,7 @@ static void WriteNodeReport(Isolate* isolate,
301316
env,
302317
"Worker thread subreport",
303318
trigger,
304-
Local<String>(),
319+
Local<Object>(),
305320
os);
306321

307322
Mutex::ScopedLock lock(workers_mutex);
@@ -455,18 +470,56 @@ static void PrintNetworkInterfaceInfo(JSONWriter* writer) {
455470
}
456471
}
457472

473+
static void PrintJavaScriptErrorProperties(JSONWriter* writer,
474+
Isolate* isolate,
475+
Local<Object> error) {
476+
writer->json_objectstart("errorProperties");
477+
if (!error.IsEmpty()) {
478+
TryCatch try_catch(isolate);
479+
Local<Context> context = error->GetIsolate()->GetCurrentContext();
480+
Local<Array> keys;
481+
if (!error->GetOwnPropertyNames(context).ToLocal(&keys)) {
482+
return writer->json_objectend(); // the end of 'errorProperties'
483+
}
484+
uint32_t keys_length = keys->Length();
485+
for (uint32_t i = 0; i < keys_length; i++) {
486+
Local<Value> key;
487+
if (!keys->Get(context, i).ToLocal(&key) || !key->IsString()) {
488+
continue;
489+
}
490+
Local<Value> value;
491+
Local<String> value_string;
492+
if (!error->Get(context, key).ToLocal(&value) ||
493+
!value->ToString(context).ToLocal(&value_string)) {
494+
continue;
495+
}
496+
String::Utf8Value k(isolate, key);
497+
if (!strcmp(*k, "stack") || !strcmp(*k, "message")) continue;
498+
String::Utf8Value v(isolate, value_string);
499+
writer->json_keyvalue(std::string(*k, k.length()),
500+
std::string(*v, v.length()));
501+
}
502+
}
503+
writer->json_objectend(); // the end of 'errorProperties'
504+
}
505+
458506
// Report the JavaScript stack.
459-
static void PrintJavaScriptStack(JSONWriter* writer,
507+
static void PrintJavaScriptErrorStack(JSONWriter* writer,
460508
Isolate* isolate,
461-
Local<String> stackstr,
509+
Local<Object> error,
462510
const char* trigger) {
463-
writer->json_objectstart("javascriptStack");
464-
465-
std::string ss;
511+
Local<Value> stackstr;
512+
std::string ss = "";
513+
TryCatch try_catch(isolate);
466514
if ((!strcmp(trigger, "FatalError")) ||
467515
(!strcmp(trigger, "Signal"))) {
468516
ss = "No stack.\nUnavailable.\n";
469-
} else {
517+
} else if (!error.IsEmpty() &&
518+
error
519+
->Get(isolate->GetCurrentContext(),
520+
node::FIXED_ONE_BYTE_STRING(isolate,
521+
"stack"))
522+
.ToLocal(&stackstr)) {
470523
String::Utf8Value sv(isolate, stackstr);
471524
ss = std::string(*sv, sv.length());
472525
}
@@ -490,7 +543,6 @@ static void PrintJavaScriptStack(JSONWriter* writer,
490543
}
491544
writer->json_arrayend();
492545
}
493-
writer->json_objectend();
494546
}
495547

496548
// Report a native stack backtrace

src/node_report.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ std::string TriggerNodeReport(v8::Isolate* isolate,
2020
const char* message,
2121
const char* trigger,
2222
const std::string& name,
23-
v8::Local<v8::String> stackstr);
23+
v8::Local<v8::Object> error);
2424
void GetNodeReport(v8::Isolate* isolate,
2525
node::Environment* env,
2626
const char* message,
2727
const char* trigger,
28-
v8::Local<v8::String> stackstr,
28+
v8::Local<v8::Object> error,
2929
std::ostream& out);
3030

3131
// Function declarations - utility functions in src/node_report_utils.cc

src/node_report_module.cc

+14-4
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,21 @@ void WriteReport(const FunctionCallbackInfo<Value>& info) {
3232
Isolate* isolate = env->isolate();
3333
HandleScope scope(isolate);
3434
std::string filename;
35-
Local<String> stackstr;
35+
Local<Object> error;
3636

3737
CHECK_EQ(info.Length(), 4);
3838
String::Utf8Value message(isolate, info[0].As<String>());
3939
String::Utf8Value trigger(isolate, info[1].As<String>());
40-
stackstr = info[3].As<String>();
4140

4241
if (info[2]->IsString())
4342
filename = *String::Utf8Value(isolate, info[2]);
43+
if (!info[3].IsEmpty() && info[3]->IsObject())
44+
error = info[3].As<Object>();
45+
else
46+
error = Local<Object>();
4447

4548
filename = TriggerNodeReport(
46-
isolate, env, *message, *trigger, filename, stackstr);
49+
isolate, env, *message, *trigger, filename, error);
4750
// Return value is the report filename
4851
info.GetReturnValue().Set(
4952
String::NewFromUtf8(isolate, filename.c_str(), v8::NewStringType::kNormal)
@@ -55,10 +58,17 @@ void GetReport(const FunctionCallbackInfo<Value>& info) {
5558
Environment* env = Environment::GetCurrent(info);
5659
Isolate* isolate = env->isolate();
5760
HandleScope scope(isolate);
61+
Local<Object> error;
5862
std::ostringstream out;
5963

64+
CHECK_EQ(info.Length(), 1);
65+
if (!info[0].IsEmpty() && info[0]->IsObject())
66+
error = info[0].As<Object>();
67+
else
68+
error = Local<Object>();
69+
6070
GetNodeReport(
61-
isolate, env, "JavaScript API", __func__, info[0].As<String>(), out);
71+
isolate, env, "JavaScript API", __func__, error, out);
6272

6373
// Return value is the contents of a report as a string.
6474
info.GetReturnValue().Set(String::NewFromUtf8(isolate,

test/common/report.js

+30-7
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ function findReports(pid, dir) {
2424
return results;
2525
}
2626

27-
function validate(filepath) {
27+
function validate(filepath, fields) {
2828
const report = fs.readFileSync(filepath, 'utf8');
2929
if (process.report.compact) {
3030
const end = report.indexOf('\n');
3131
assert.strictEqual(end, report.length - 1);
3232
}
33-
validateContent(JSON.parse(report));
33+
validateContent(JSON.parse(report), fields);
3434
}
3535

36-
function validateContent(report) {
36+
function validateContent(report, fields = []) {
3737
if (typeof report === 'string') {
3838
try {
3939
report = JSON.parse(report);
@@ -43,7 +43,7 @@ function validateContent(report) {
4343
}
4444
}
4545
try {
46-
_validateContent(report);
46+
_validateContent(report, fields);
4747
} catch (err) {
4848
try {
4949
err.stack += util.format('\n------\nFailing Report:\n%O', report);
@@ -52,7 +52,7 @@ function validateContent(report) {
5252
}
5353
}
5454

55-
function _validateContent(report) {
55+
function _validateContent(report, fields = []) {
5656
const isWindows = process.platform === 'win32';
5757

5858
// Verify that all sections are present as own properties of the report.
@@ -71,6 +71,26 @@ function _validateContent(report) {
7171
assert(typeof report[section] === 'object' && report[section] !== null);
7272
});
7373

74+
fields.forEach((field) => {
75+
function checkLoop(actual, rest, expect) {
76+
actual = actual[rest.shift()];
77+
if (rest.length === 0 && actual !== undefined) {
78+
assert.strictEqual(actual, expect);
79+
} else {
80+
assert(actual);
81+
checkLoop(actual, rest, expect);
82+
}
83+
}
84+
let actual, expect;
85+
if (Array.isArray(field)) {
86+
[actual, expect] = field;
87+
} else {
88+
actual = field;
89+
expect = undefined;
90+
}
91+
checkLoop(report, actual.split('.'), expect);
92+
});
93+
7494
// Verify the format of the header section.
7595
const header = report.header;
7696
const headerFields = ['event', 'trigger', 'filename', 'dumpEventTime',
@@ -144,7 +164,10 @@ function _validateContent(report) {
144164
assert.strictEqual(header.host, os.hostname());
145165

146166
// Verify the format of the javascriptStack section.
147-
checkForUnknownFields(report.javascriptStack, ['message', 'stack']);
167+
checkForUnknownFields(report.javascriptStack,
168+
['message', 'stack', 'errorProperties']);
169+
assert.strictEqual(typeof report.javascriptStack.errorProperties,
170+
'object');
148171
assert.strictEqual(typeof report.javascriptStack.message, 'string');
149172
if (report.javascriptStack.stack !== undefined) {
150173
assert(Array.isArray(report.javascriptStack.stack));
@@ -262,7 +285,7 @@ function _validateContent(report) {
262285

263286
// Verify the format of the workers section.
264287
assert(Array.isArray(report.workers));
265-
report.workers.forEach(_validateContent);
288+
report.workers.forEach((worker) => _validateContent(worker));
266289
}
267290

268291
function checkForUnknownFields(actual, expected) {

test/report/test-report-getreport.js

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ const helper = require('../common/report');
2323
assert.deepStrictEqual(helper.findReports(process.pid, process.cwd()), []);
2424
}
2525

26+
{
27+
const error = new Error();
28+
error.foo = 'goo';
29+
helper.validateContent(process.report.getReport(error),
30+
[['javascriptStack.errorProperties.foo', 'goo']]);
31+
}
32+
2633
// Test with an invalid error argument.
2734
[null, 1, Symbol(), function() {}, 'foo'].forEach((error) => {
2835
assert.throws(() => {

test/report/test-report-writereport.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ process.report.directory = tmpdir.path;
1515
function validate() {
1616
const reports = helper.findReports(process.pid, tmpdir.path);
1717
assert.strictEqual(reports.length, 1);
18-
helper.validate(reports[0]);
18+
helper.validate(reports[0], arguments[0]);
1919
fs.unlinkSync(reports[0]);
2020
return reports[0];
2121
}
@@ -40,6 +40,13 @@ function validate() {
4040
validate();
4141
}
4242

43+
{
44+
const error = new Error();
45+
error.foo = 'goo';
46+
process.report.writeReport(error);
47+
validate([['javascriptStack.errorProperties.foo', 'goo']]);
48+
}
49+
4350
{
4451
// Test with a file argument.
4552
const file = process.report.writeReport('custom-name-1.json');

0 commit comments

Comments
 (0)