-
-
Notifications
You must be signed in to change notification settings - Fork 36
Allow Error object to be passed to node-report #82
Changes from 5 commits
15fd45a
c73968a
5b75c5e
e5edd5f
cee0a31
7130888
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,8 @@ NAN_METHOD(TriggerReport) { | |
| Nan::HandleScope scope; | ||
| v8::Isolate* isolate = info.GetIsolate(); | ||
| char filename[NR_MAXNAME + 1] = ""; | ||
| v8::MaybeLocal<v8::Value> error; | ||
| int err_index = 0; | ||
|
|
||
| if (info[0]->IsString()) { | ||
| // Filename parameter supplied | ||
|
|
@@ -60,9 +62,16 @@ NAN_METHOD(TriggerReport) { | |
| } else { | ||
| Nan::ThrowError("node-report: filename parameter is too long"); | ||
| } | ||
| err_index++; | ||
| } | ||
|
|
||
| // We need to pass the javascript object so we can query if for a stack trace. | ||
| if (info[err_index]->IsNativeError()) { | ||
| error = info[err_index]; | ||
| } | ||
|
|
||
| if (nodereport_events & NR_APICALL) { | ||
| TriggerNodeReport(isolate, kJavaScript, "JavaScript API", __func__, filename); | ||
| TriggerNodeReport(isolate, kJavaScript, "JavaScript API", __func__, filename, error); | ||
| // Return value is the report filename | ||
| info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked()); | ||
| } | ||
|
|
@@ -77,7 +86,12 @@ NAN_METHOD(GetReport) { | |
| v8::Isolate* isolate = info.GetIsolate(); | ||
| std::ostringstream out; | ||
|
|
||
| GetNodeReport(isolate, kJavaScript, "JavaScript API", __func__, out); | ||
| v8::MaybeLocal<v8::Value> error; | ||
| if (info[0]->IsNativeError()) { | ||
| error = info[0]; | ||
| } | ||
|
|
||
| GetNodeReport(isolate, kJavaScript, "JavaScript API", __func__, error, out); | ||
| // Return value is the contents of a report as a string. | ||
| info.GetReturnValue().Set(Nan::New(out.str()).ToLocalChecked()); | ||
| } | ||
|
|
@@ -156,7 +170,7 @@ static void OnFatalError(const char* location, const char* message) { | |
| } | ||
| // Trigger report if requested | ||
| if (nodereport_events & NR_FATALERROR) { | ||
| TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, nullptr); | ||
| TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, nullptr, v8::MaybeLocal<v8::Value>()); | ||
|
||
| } | ||
| fflush(stderr); | ||
| raise(SIGABRT); | ||
|
|
@@ -165,7 +179,7 @@ static void OnFatalError(const char* location, const char* message) { | |
| bool OnUncaughtException(v8::Isolate* isolate) { | ||
| // Trigger report if requested | ||
| if (nodereport_events & NR_EXCEPTION) { | ||
| TriggerNodeReport(isolate, kException, "exception", __func__, nullptr); | ||
| TriggerNodeReport(isolate, kException, "exception", __func__, nullptr, v8::MaybeLocal<v8::Value>()); | ||
| } | ||
| if ((commandline_string.find("abort-on-uncaught-exception") != std::string::npos) || | ||
| (commandline_string.find("abort_on_uncaught_exception") != std::string::npos)) { | ||
|
|
@@ -231,7 +245,7 @@ static void SignalDumpInterruptCallback(Isolate* isolate, void* data) { | |
| fprintf(stdout, "node-report: SignalDumpInterruptCallback triggering report\n"); | ||
| } | ||
| TriggerNodeReport(isolate, kSignal_JS, | ||
| node::signo_string(report_signal), __func__, nullptr); | ||
| node::signo_string(report_signal), __func__, nullptr, v8::MaybeLocal<v8::Value>()); | ||
| } | ||
| report_signal = 0; | ||
| } | ||
|
|
@@ -246,7 +260,7 @@ static void SignalDumpAsyncCallback(uv_async_t* handle) { | |
| fprintf(stdout, "node-report: SignalDumpAsyncCallback triggering NodeReport\n"); | ||
| } | ||
| TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV, | ||
| node::signo_string(report_signal), __func__, nullptr); | ||
| node::signo_string(report_signal), __func__, nullptr, v8::MaybeLocal<v8::Value>()); | ||
|
||
| } | ||
| report_signal = 0; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,10 +82,11 @@ typedef struct tm TIME_TYPE; | |
| #endif | ||
|
|
||
| // Internal/static function declarations | ||
| static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, TIME_TYPE* time); | ||
| static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, v8::MaybeLocal<v8::Value> error, TIME_TYPE* time); | ||
| static void PrintCommandLine(std::ostream& out); | ||
| static void PrintVersionInformation(std::ostream& out); | ||
| static void PrintJavaScriptStack(std::ostream& out, Isolate* isolate, DumpEvent event, const char* location); | ||
| static void PrintJavaScriptErrorStack(std::ostream& out, Isolate* isolate, v8::MaybeLocal<v8::Value> error); | ||
| static void PrintStackFromStackTrace(std::ostream& out, Isolate* isolate, DumpEvent event); | ||
| static void PrintStackFrame(std::ostream& out, Isolate* isolate, Local<StackFrame> frame, int index, void* pc); | ||
| static void PrintNativeStack(std::ostream& out); | ||
|
|
@@ -379,7 +380,7 @@ void SetCommandLine() { | |
| * const char* location | ||
| * char* name - in/out - returns the report filename | ||
| ******************************************************************************/ | ||
| void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name) { | ||
| void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name, v8::MaybeLocal<v8::Value> error) { | ||
|
||
| // Recursion check for report in progress, bail out | ||
| if (report_active) return; | ||
| report_active = true; | ||
|
|
@@ -460,7 +461,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c | |
| // Pass our stream about by reference, not by copying it. | ||
| std::ostream &out = outfile.is_open() ? outfile : *outstream; | ||
|
|
||
| WriteNodeReport(isolate, event, message, location, filename, out, &tm_struct); | ||
| WriteNodeReport(isolate, event, message, location, filename, out, error, &tm_struct); | ||
|
|
||
| // Do not close stdout/stderr, only close files we opened. | ||
| if(outfile.is_open()) { | ||
|
|
@@ -474,7 +475,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c | |
|
|
||
| } | ||
|
|
||
| void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, std::ostream& out) { | ||
| void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, v8::MaybeLocal<v8::Value> error, std::ostream& out) { | ||
| // Obtain the current time and the pid (platform dependent) | ||
| TIME_TYPE tm_struct; | ||
| #ifdef _WIN32 | ||
|
|
@@ -484,7 +485,7 @@ void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const | |
| gettimeofday(&time_val, nullptr); | ||
| localtime_r(&time_val.tv_sec, &tm_struct); | ||
| #endif | ||
| WriteNodeReport(isolate, event, message, location, nullptr, out, &tm_struct); | ||
| WriteNodeReport(isolate, event, message, location, nullptr, out, error, &tm_struct); | ||
| } | ||
|
|
||
| static void walkHandle(uv_handle_t* h, void* arg) { | ||
|
|
@@ -531,7 +532,7 @@ static void walkHandle(uv_handle_t* h, void* arg) { | |
| *out << buf; | ||
| } | ||
|
|
||
| static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, TIME_TYPE* tm_struct) { | ||
| static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* filename, std::ostream &out, v8::MaybeLocal<v8::Value> error, TIME_TYPE* tm_struct) { | ||
|
|
||
| #ifdef _WIN32 | ||
| DWORD pid = GetCurrentProcessId(); | ||
|
|
@@ -589,6 +590,11 @@ static void WriteNodeReport(Isolate* isolate, DumpEvent event, const char* messa | |
| PrintNativeStack(out); | ||
| out << std::flush; | ||
|
|
||
| // Print the stack trace and message from the Error object. | ||
| // (If one was provided.) | ||
| PrintJavaScriptErrorStack(out, isolate, error); | ||
| out << std::flush; | ||
|
|
||
| // Print V8 Heap and Garbage Collector information | ||
| PrintGCStatistics(out, isolate); | ||
| out << std::flush; | ||
|
|
@@ -791,6 +797,29 @@ static void PrintJavaScriptStack(std::ostream& out, Isolate* isolate, DumpEvent | |
| #endif | ||
| } | ||
|
|
||
| static void PrintJavaScriptErrorStack(std::ostream& out, Isolate* isolate, v8::MaybeLocal<v8::Value> error) { | ||
| if (error.IsEmpty() || !error.ToLocalChecked()->IsNativeError()) { | ||
| return; | ||
| } | ||
|
|
||
| out << "\n================================================================================"; | ||
| out << "\n==== JavaScript Exception Details ==============================================\n\n"; | ||
| Local<Message> message = v8::Exception::CreateMessage(isolate, error.ToLocalChecked()); | ||
| Nan::Utf8String message_str(message->Get()); | ||
|
|
||
| out << *message_str << "\n\n"; | ||
|
|
||
| Local<StackTrace> stack = v8::Exception::GetStackTrace(error.ToLocalChecked()); | ||
| if (stack.IsEmpty()) { | ||
| out << "No stack trace available from Exception::GetStackTrace()\n"; | ||
| return; | ||
| } | ||
| // Print the stack trace, samples are not available as the exception isn't from the current stack. | ||
| for (int i = 0; i < stack->GetFrameCount(); i++) { | ||
| PrintStackFrame(out, isolate, stack->GetFrame(i), i, nullptr); | ||
| } | ||
| } | ||
|
|
||
| /******************************************************************************* | ||
| * Function to print stack using GetStackSample() and StackTrace::StackTrace() | ||
| * | ||
|
|
@@ -842,13 +871,14 @@ static void PrintStackFrame(std::ostream& out, Isolate* isolate, Local<StackFram | |
| char buf[64]; | ||
|
|
||
| // First print the frame index and the instruction address | ||
| if (pc != nullptr) { | ||
| #ifdef _WIN32 | ||
| snprintf( buf, sizeof(buf), "%2d: [pc=0x%p] ", i, pc); | ||
| out << buf; | ||
| snprintf( buf, sizeof(buf), "%2d: [pc=0x%p] ", i, pc); | ||
| #else | ||
| snprintf( buf, sizeof(buf), "%2d: [pc=%p] ", i, pc); | ||
| out << buf; | ||
| snprintf( buf, sizeof(buf), "%2d: [pc=%p] ", i, pc); | ||
| #endif | ||
| out << buf; | ||
| } | ||
|
|
||
| // Now print the JavaScript function name and source information | ||
| if (frame->IsEval()) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| 'use strict'; | ||
|
|
||
| // Testcase for passing an error object to the API call. | ||
|
|
||
| if (process.argv[2] === 'child') { | ||
| const nodereport = require('../'); | ||
| try { | ||
| throw new Error("Testing error handling"); | ||
| } catch (err) { | ||
| nodereport.triggerReport(err); | ||
| } | ||
| } else { | ||
| const common = require('./common.js'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can just be
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested, it looks like it can. @sam-github had other comments about the test structure. At the moment it matches all the other tests and if we want to fix one up we should probably do all of them under a separate PR so I'm going to leave this for now. |
||
| const spawn = require('child_process').spawn; | ||
| const tap = require('tap'); | ||
|
|
||
| const child = spawn(process.execPath, [__filename, 'child']); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see why the test is spawning a child to call triggerReport(), why not just call it directly in the test? It looks like boilerplate copied from a test that must exist in order to get the report that doesn't apply here. If it is needed, probably needs a comment explaining why.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe most of the tests use spawn so the standard validation checks in common.js can check a known set of process arguments. I think @richardlau would be the person to ask if you want the history. The test is a direct copy of test-api.js with a small change to how the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, part of the standard validation checks in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although the command line check can be skipped by not setting
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sam-github Do you want this changed before you approve? I'm happy to raise a separate PR about changing the tests. |
||
| child.on('exit', (code) => { | ||
| tap.plan(3); | ||
| tap.equal(code, 0, 'Process exited cleanly'); | ||
| const reports = common.findReports(child.pid); | ||
| tap.equal(reports.length, 1, 'Found reports ' + reports); | ||
| const report = reports[0]; | ||
| common.validate(tap, report, {pid: child.pid, | ||
| commandline: child.spawnargs.join(' '), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was half convinced by the argument for spawning above, then I read test/common.js, and now I'm not. The command line is just checked against what is passed in here, which could just as easily be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the problems we had with But as I pointed out elsewhere the command line check is optional -- We can leave out |
||
| expectedException: "Testing error handling", | ||
| }); | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
javascript -> JavaScript and if -> it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.