Skip to content
This repository was archived by the owner on Jun 18, 2021. It is now read-only.

Commit b20b236

Browse files
hhellyerrnchamberlain
authored andcommitted
Allow Error object to be passed to node-report
Add an optional parameter to triggerReport and getReport so an Error object can be passed. If it is passed the error message and stack trace it contains will be added to the node-report output in a new "JavaScript Exception Details" section. This makes node-report more useful in custom error handlers as it will include the stack trace of the original error as well as the stack trace where the error was handled. PR-URL: #82 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Sam Roberts <[email protected]> Reviewed-By: Richard Lau <[email protected]> Reviewed-By: Richard Chamberlain <[email protected]>
1 parent 1b62e31 commit b20b236

File tree

6 files changed

+125
-19
lines changed

6 files changed

+125
-19
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,26 @@ can be specified as a parameter on the `triggerReport()` call.
6969
nodereport.triggerReport("myReportName");
7070
```
7171

72+
Both `triggerReport()` and `getReport()` can take an optional `Error` object
73+
as a parameter. If an `Error` object is provided, the message and stack trace
74+
from the object will be included in the report in the `JavaScript Exception
75+
Details` section.
76+
When using node-report to handle errors in a callback or an exception handler
77+
this allows the report to include the location of the original error as well
78+
as where it was handled.
79+
If both a filename and `Error` object are passed to `triggerReport()` the
80+
`Error` object should be the second parameter.
81+
82+
```js
83+
try {
84+
process.chdir('/foo/foo');
85+
} catch (err) {
86+
nodereport.triggerReport(err);
87+
}
88+
...
89+
});
90+
```
91+
7292
## Configuration
7393

7494
Additional configuration is available using the following APIs:

src/module.cc

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ NAN_METHOD(TriggerReport) {
5151
Nan::HandleScope scope;
5252
v8::Isolate* isolate = info.GetIsolate();
5353
char filename[NR_MAXNAME + 1] = "";
54+
MaybeLocal<Value> error;
55+
int err_index = 0;
5456

5557
if (info[0]->IsString()) {
5658
// Filename parameter supplied
@@ -60,9 +62,16 @@ NAN_METHOD(TriggerReport) {
6062
} else {
6163
Nan::ThrowError("node-report: filename parameter is too long");
6264
}
65+
err_index++;
6366
}
67+
68+
// We need to pass the JavaScript object so we can query it for a stack trace.
69+
if (info[err_index]->IsNativeError()) {
70+
error = info[err_index];
71+
}
72+
6473
if (nodereport_events & NR_APICALL) {
65-
TriggerNodeReport(isolate, kJavaScript, "JavaScript API", __func__, filename);
74+
TriggerNodeReport(isolate, kJavaScript, "JavaScript API", __func__, filename, error);
6675
// Return value is the report filename
6776
info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked());
6877
}
@@ -77,7 +86,12 @@ NAN_METHOD(GetReport) {
7786
v8::Isolate* isolate = info.GetIsolate();
7887
std::ostringstream out;
7988

80-
GetNodeReport(isolate, kJavaScript, "JavaScript API", __func__, out);
89+
MaybeLocal<Value> error;
90+
if (info[0]->IsNativeError()) {
91+
error = info[0];
92+
}
93+
94+
GetNodeReport(isolate, kJavaScript, "JavaScript API", __func__, error, out);
8195
// Return value is the contents of a report as a string.
8296
info.GetReturnValue().Set(Nan::New(out.str()).ToLocalChecked());
8397
}
@@ -156,7 +170,7 @@ static void OnFatalError(const char* location, const char* message) {
156170
}
157171
// Trigger report if requested
158172
if (nodereport_events & NR_FATALERROR) {
159-
TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, nullptr);
173+
TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, nullptr, MaybeLocal<Value>());
160174
}
161175
fflush(stderr);
162176
raise(SIGABRT);
@@ -165,7 +179,7 @@ static void OnFatalError(const char* location, const char* message) {
165179
bool OnUncaughtException(v8::Isolate* isolate) {
166180
// Trigger report if requested
167181
if (nodereport_events & NR_EXCEPTION) {
168-
TriggerNodeReport(isolate, kException, "exception", __func__, nullptr);
182+
TriggerNodeReport(isolate, kException, "exception", __func__, nullptr, MaybeLocal<Value>());
169183
}
170184
if ((commandline_string.find("abort-on-uncaught-exception") != std::string::npos) ||
171185
(commandline_string.find("abort_on_uncaught_exception") != std::string::npos)) {
@@ -231,7 +245,7 @@ static void SignalDumpInterruptCallback(Isolate* isolate, void* data) {
231245
fprintf(stdout, "node-report: SignalDumpInterruptCallback triggering report\n");
232246
}
233247
TriggerNodeReport(isolate, kSignal_JS,
234-
node::signo_string(report_signal), __func__, nullptr);
248+
node::signo_string(report_signal), __func__, nullptr, MaybeLocal<Value>());
235249
}
236250
report_signal = 0;
237251
}
@@ -246,7 +260,7 @@ static void SignalDumpAsyncCallback(uv_async_t* handle) {
246260
fprintf(stdout, "node-report: SignalDumpAsyncCallback triggering NodeReport\n");
247261
}
248262
TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV,
249-
node::signo_string(report_signal), __func__, nullptr);
263+
node::signo_string(report_signal), __func__, nullptr, MaybeLocal<Value>());
250264
}
251265
report_signal = 0;
252266
}

src/node_report.cc

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ typedef struct tm TIME_TYPE;
8282
#endif
8383

8484
// Internal/static function declarations
85-
static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, TIME_TYPE* time);
85+
static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, MaybeLocal<Value> error, TIME_TYPE* time);
8686
static void PrintCommandLine(std::ostream& out);
8787
static void PrintVersionInformation(std::ostream& out);
8888
static void PrintJavaScriptStack(std::ostream& out, Isolate* isolate, DumpEvent event, const char* location);
89+
static void PrintJavaScriptErrorStack(std::ostream& out, Isolate* isolate, MaybeLocal<Value> error);
8990
static void PrintStackFromStackTrace(std::ostream& out, Isolate* isolate, DumpEvent event);
9091
static void PrintStackFrame(std::ostream& out, Isolate* isolate, Local<StackFrame> frame, int index, void* pc);
9192
static void PrintNativeStack(std::ostream& out);
@@ -378,8 +379,9 @@ void SetCommandLine() {
378379
* const char* message
379380
* const char* location
380381
* char* name - in/out - returns the report filename
382+
* MaybeLocal<Value> error - JavaScript Error object.
381383
******************************************************************************/
382-
void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name) {
384+
void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name, MaybeLocal<Value> error) {
383385
// Recursion check for report in progress, bail out
384386
if (report_active) return;
385387
report_active = true;
@@ -460,7 +462,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c
460462
// Pass our stream about by reference, not by copying it.
461463
std::ostream &out = outfile.is_open() ? outfile : *outstream;
462464

463-
WriteNodeReport(isolate, event, message, location, filename, out, &tm_struct);
465+
WriteNodeReport(isolate, event, message, location, filename, out, error, &tm_struct);
464466

465467
// Do not close stdout/stderr, only close files we opened.
466468
if(outfile.is_open()) {
@@ -474,7 +476,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c
474476

475477
}
476478

477-
void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, std::ostream& out) {
479+
void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, MaybeLocal<Value> error, std::ostream& out) {
478480
// Obtain the current time and the pid (platform dependent)
479481
TIME_TYPE tm_struct;
480482
#ifdef _WIN32
@@ -484,7 +486,7 @@ void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const
484486
gettimeofday(&time_val, nullptr);
485487
localtime_r(&time_val.tv_sec, &tm_struct);
486488
#endif
487-
WriteNodeReport(isolate, event, message, location, nullptr, out, &tm_struct);
489+
WriteNodeReport(isolate, event, message, location, nullptr, out, error, &tm_struct);
488490
}
489491

490492
static void walkHandle(uv_handle_t* h, void* arg) {
@@ -531,7 +533,7 @@ static void walkHandle(uv_handle_t* h, void* arg) {
531533
*out << buf;
532534
}
533535

534-
static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, TIME_TYPE* tm_struct) {
536+
static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, MaybeLocal<Value> error, TIME_TYPE* tm_struct) {
535537

536538
#ifdef _WIN32
537539
DWORD pid = GetCurrentProcessId();
@@ -589,6 +591,11 @@ static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* messa
589591
PrintNativeStack(out);
590592
out << std::flush;
591593

594+
// Print the stack trace and message from the Error object.
595+
// (If one was provided.)
596+
PrintJavaScriptErrorStack(out, isolate, error);
597+
out << std::flush;
598+
592599
// Print V8 Heap and Garbage Collector information
593600
PrintGCStatistics(out, isolate);
594601
out << std::flush;
@@ -791,6 +798,29 @@ static void PrintJavaScriptStack(std::ostream& out, Isolate* isolate, DumpEvent
791798
#endif
792799
}
793800

801+
static void PrintJavaScriptErrorStack(std::ostream& out, Isolate* isolate, MaybeLocal<Value> error) {
802+
if (error.IsEmpty() || !error.ToLocalChecked()->IsNativeError()) {
803+
return;
804+
}
805+
806+
out << "\n================================================================================";
807+
out << "\n==== JavaScript Exception Details ==============================================\n\n";
808+
Local<Message> message = v8::Exception::CreateMessage(isolate, error.ToLocalChecked());
809+
Nan::Utf8String message_str(message->Get());
810+
811+
out << *message_str << "\n\n";
812+
813+
Local<StackTrace> stack = v8::Exception::GetStackTrace(error.ToLocalChecked());
814+
if (stack.IsEmpty()) {
815+
out << "No stack trace available from Exception::GetStackTrace()\n";
816+
return;
817+
}
818+
// Print the stack trace, samples are not available as the exception isn't from the current stack.
819+
for (int i = 0; i < stack->GetFrameCount(); i++) {
820+
PrintStackFrame(out, isolate, stack->GetFrame(i), i, nullptr);
821+
}
822+
}
823+
794824
/*******************************************************************************
795825
* Function to print stack using GetStackSample() and StackTrace::StackTrace()
796826
*
@@ -842,13 +872,14 @@ static void PrintStackFrame(std::ostream& out, Isolate* isolate, Local<StackFram
842872
char buf[64];
843873

844874
// First print the frame index and the instruction address
875+
if (pc != nullptr) {
845876
#ifdef _WIN32
846-
snprintf( buf, sizeof(buf), "%2d: [pc=0x%p] ", i, pc);
847-
out << buf;
877+
snprintf( buf, sizeof(buf), "%2d: [pc=0x%p] ", i, pc);
848878
#else
849-
snprintf( buf, sizeof(buf), "%2d: [pc=%p] ", i, pc);
850-
out << buf;
879+
snprintf( buf, sizeof(buf), "%2d: [pc=%p] ", i, pc);
851880
#endif
881+
out << buf;
882+
}
852883

853884
// Now print the JavaScript function name and source information
854885
if (frame->IsEval()) {

src/node_report.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ using v8::String;
1919
using v8::Value;
2020
using v8::StackTrace;
2121
using v8::StackFrame;
22+
using v8::MaybeLocal;
2223

2324
// Bit-flags for node-report trigger options
2425
#define NR_EXCEPTION 0x01
@@ -32,8 +33,8 @@ using v8::StackFrame;
3233

3334
enum DumpEvent {kException, kFatalError, kSignal_JS, kSignal_UV, kJavaScript};
3435

35-
void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name);
36-
void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, std::ostream& out);
36+
void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name, v8::MaybeLocal<v8::Value> error);
37+
void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, v8::MaybeLocal<v8::Value> error, std::ostream& out);
3738

3839
unsigned int ProcessNodeReportEvents(const char* args);
3940
unsigned int ProcessNodeReportSignal(const char* args);

test/common.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,18 @@ exports.validateContent = function validateContent(data, t, options) {
5858
const expectedVersions = options ?
5959
options.expectedVersions || nodeComponents :
6060
nodeComponents;
61-
var plan = REPORT_SECTIONS.length + nodeComponents.length + 5;
61+
const expectedException = options.expectedException;
62+
if (options.expectedException) {
63+
REPORT_SECTIONS.push('JavaScript Exception Details');
64+
}
65+
66+
let plan = REPORT_SECTIONS.length + nodeComponents.length + 5;
6267
if (options.commandline) plan++;
68+
if (options.expectedException) plan++;
6369
const glibcRE = /\(glibc:\s([\d.]+)/;
6470
const nodeReportSection = getSection(reportContents, 'Node Report');
6571
const sysInfoSection = getSection(reportContents, 'System Information');
72+
const exceptionSection = getSection(reportContents, 'JavaScript Exception Details');
6673
const libcPath = getLibcPath(sysInfoSection);
6774
const libcVersion = getLibcVersion(libcPath);
6875
if (glibcRE.test(nodeReportSection) && libcVersion) plan++;
@@ -84,6 +91,10 @@ exports.validateContent = function validateContent(data, t, options) {
8491
new RegExp('Node.js version: ' + process.version),
8592
'Node Report header section contains expected Node.js version');
8693
}
94+
if (options && options.expectedException) {
95+
t.match(exceptionSection, new RegExp('Uncaught Error: ' + options.expectedException),
96+
'Node Report JavaScript Exception contains expected message');
97+
}
8798
if (options && options.commandline) {
8899
if (this.isWindows()) {
89100
// On Windows we need to strip double quotes from the command line in

test/test-api-pass-error.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
// Testcase for passing an error object to the API call.
4+
5+
if (process.argv[2] === 'child') {
6+
const nodereport = require('../');
7+
try {
8+
throw new Error("Testing error handling");
9+
} catch (err) {
10+
nodereport.triggerReport(err);
11+
}
12+
} else {
13+
const common = require('./common.js');
14+
const spawn = require('child_process').spawn;
15+
const tap = require('tap');
16+
17+
const child = spawn(process.execPath, [__filename, 'child']);
18+
child.on('exit', (code) => {
19+
tap.plan(3);
20+
tap.equal(code, 0, 'Process exited cleanly');
21+
const reports = common.findReports(child.pid);
22+
tap.equal(reports.length, 1, 'Found reports ' + reports);
23+
const report = reports[0];
24+
common.validate(tap, report, {pid: child.pid,
25+
commandline: child.spawnargs.join(' '),
26+
expectedException: "Testing error handling",
27+
});
28+
});
29+
}

0 commit comments

Comments
 (0)