Skip to content
This repository was archived by the owner on Jun 18, 2021. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ Delivers a human-readable diagnostic summary, written to file.
The report is intended for development, test and production
use, to capture and preserve information for problem determination.
It includes JavaScript and native stack traces, heap statistics,
platform information and resource usage etc. With the report enabled,
reports can be triggered on unhandled exceptions, fatal errors, signals
and calls to a JavaScript API.
platform information and resource usage etc. Reports can be triggered on
unhandled exceptions, fatal errors, signals and calls to JavaScript APIs.

Supports Node.js v4, v6 and v7 on AIX, Linux, MacOS, SmartOS and Windows.
Supports Node.js version 4, 6 and 8 on AIX, Linux, MacOS, SmartOS and Windows.

## Usage

```bash
npm install node-report
node -r node-report app.js
```
A report will be triggered automatically on unhandled exceptions and fatal
error events (for example out of memory errors), and can also be triggered
by sending a USR2 signal to a Node.js process (not supported on Windows).
A report will be triggered automatically on unhandled exceptions, fatal errors
(for example out of memory errors) and crashes in native code. A report can also be
triggered by sending a USR2 signal to a Node.js process (not supported on Windows).

A report can also be triggered via an API call from a JavaScript
application.
Expand All @@ -37,7 +36,7 @@ var report_str = nodereport.getReport();
console.log(report_str);
```
The API can be used without adding the automatic exception and fatal error
hooks and the signal handler, as follows:
hooks and the signal handlers, as follows:

```js
var nodereport = require('node-report/api');
Expand Down Expand Up @@ -95,7 +94,7 @@ Additional configuration is available using the following APIs:

```js
var nodereport = require('node-report/api');
nodereport.setEvents("exception+fatalerror+signal+apicall");
nodereport.setEvents("exception+fatalerror+signal+apicall+crash");
nodereport.setSignal("SIGUSR2|SIGQUIT");
nodereport.setFileName("stdout|stderr|<filename>");
nodereport.setDirectory("<full path>");
Expand All @@ -105,7 +104,7 @@ nodereport.setVerbose("yes|no");
Configuration on module initialization is also available via environment variables:

```bash
export NODEREPORT_EVENTS=exception+fatalerror+signal+apicall
export NODEREPORT_EVENTS=exception+fatalerror+signal+apicall+crash
export NODEREPORT_SIGNAL=SIGUSR2|SIGQUIT
export NODEREPORT_FILENAME=stdout|stderr|<filename>
export NODEREPORT_DIRECTORY=<full path>
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const api = require('./api');

// NODEREPORT_EVENTS env var overrides the defaults
const options = process.env.NODEREPORT_EVENTS || 'exception+fatalerror+signal+apicall';
const options = process.env.NODEREPORT_EVENTS || 'exception+fatalerror+signal+apicall+crash';
api.setEvents(options);

exports.triggerReport = api.triggerReport;
Expand Down
74 changes: 61 additions & 13 deletions src/module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ static void PrintStackFromStackTrace(Isolate* isolate, FILE* fp);
static void SignalDumpAsyncCallback(uv_async_t* handle);
inline void* ReportSignalThreadMain(void* unused);
static int StartWatchdogThread(void* (*thread_main)(void* unused));
static void RegisterSignalHandler(int signo, void (*handler)(int),
static void RegisterSignalHandler(int signo, void (*handler)(int, siginfo_t*, void*),
struct sigaction* saved_sa);
static void RestoreSignalHandler(int signo, struct sigaction* saved_sa);
static void SignalDump(int signo);
static void SignalHandler(int signo, siginfo_t* info, void* void_context);
static void CrashHandler(int signo, siginfo_t* info, void* void_context);
static void SetupSignalHandler();
#endif

Expand All @@ -31,7 +32,10 @@ static int report_signal = 0; // atomic for signal handling in progress
static uv_sem_t report_semaphore; // semaphore for hand-off to watchdog
static uv_async_t nodereport_trigger_async; // async handle for event loop
static uv_mutex_t node_isolate_mutex; // mutex for watchdog thread
static struct sigaction saved_sa; // saved signal action
static struct sigaction saved_user_sa; // saved user signal action
static struct sigaction saved_sigsegv_sa; // saved crash signal action
static struct sigaction saved_sigill_sa; // saved crash signal action
static struct sigaction saved_sigfpe_sa; // saved crash signal action
#endif

// State variables for v8 hooks and signal initialisation
Expand Down Expand Up @@ -128,7 +132,20 @@ NAN_METHOD(SetEvents) {
}
// If report no longer required on external user signal, reset the OS signal handler
if (!(nodereport_events & NR_SIGNAL) && (previous_events & NR_SIGNAL)) {
RestoreSignalHandler(nodereport_signal, &saved_sa);
RestoreSignalHandler(nodereport_signal, &saved_user_sa);
}

// If report newly requested on native crash, set up signal handlers
if ((nodereport_events & NR_CRASH) && !(previous_events & NR_CRASH)) {
RegisterSignalHandler(SIGSEGV, CrashHandler, &saved_sigsegv_sa);
RegisterSignalHandler(SIGILL, CrashHandler, &saved_sigill_sa);
RegisterSignalHandler(SIGFPE, CrashHandler, &saved_sigfpe_sa);
}
// If report no longer required on native crash, reset the signal handlers
if (!(nodereport_events & NR_CRASH) && (previous_events & NR_CRASH)) {
RestoreSignalHandler(SIGSEGV, &saved_sigsegv_sa);
RestoreSignalHandler(SIGILL, &saved_sigill_sa);
RestoreSignalHandler(SIGFPE, &saved_sigfpe_sa);
}
#endif
}
Expand All @@ -140,8 +157,8 @@ NAN_METHOD(SetSignal) {

// If signal event active and selected signal has changed, switch the OS signal handler
if ((nodereport_events & NR_SIGNAL) && (nodereport_signal != previous_signal)) {
RestoreSignalHandler(previous_signal, &saved_sa);
RegisterSignalHandler(nodereport_signal, SignalDump, &saved_sa);
RestoreSignalHandler(previous_signal, &saved_user_sa);
RegisterSignalHandler(nodereport_signal, SignalHandler, &saved_user_sa);
}
#endif
}
Expand Down Expand Up @@ -268,18 +285,20 @@ static void SignalDumpAsyncCallback(uv_async_t* handle) {

/*******************************************************************************
* Utility functions for signal handling support (platforms except Windows)
* - RegisterSignalHandler() - register a raw OS signal handler
* - SignalDump() - implementation of raw OS signal handler
* - RegisterSignalHandler() - register an OS signal handler
* - SignalHandler() - implementation of OS signal handler for external signals
* - CrashHandler() - implementation of OS signal handler for crash signals
* - StartWatchdogThread() - create a watchdog thread
* - ReportSignalThreadMain() - implementation of watchdog thread
* - SetupSignalHandler() - initialisation of signal handlers and threads
******************************************************************************/
// Utility function to register an OS signal handler
static void RegisterSignalHandler(int signo, void (*handler)(int),
static void RegisterSignalHandler(int signo, void (*handler)(int, siginfo_t*, void*),
struct sigaction* saved_sa) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
sigfillset(&sa.sa_mask); // mask all signals while in the handler
sigaction(signo, &sa, saved_sa);
}
Expand All @@ -289,14 +308,37 @@ static void RestoreSignalHandler(int signo, struct sigaction* saved_sa) {
sigaction(signo, saved_sa, nullptr);
}

// Raw signal handler for triggering a report - runs on an arbitrary thread
static void SignalDump(int signo) {
// Native signal handler for triggering a report on user signal - runs on an arbitrary thread
static void SignalHandler(int signo, siginfo_t* info, void* void_context) {
// Check atomic for report already pending, storing the signal number
if (__sync_val_compare_and_swap(&report_signal, 0, signo) == 0) {
uv_sem_post(&report_semaphore); // Hand-off to watchdog thread
}
}

// Native signal handler for triggering a report on a crash, runs on crashing thread
static void CrashHandler(int signo, siginfo_t* info, void* void_context) {
// Remove node-report crash handlers in case we get a secondary crash
RestoreSignalHandler(signo, &saved_sigsegv_sa);
RestoreSignalHandler(signo, &saved_sigill_sa);
RestoreSignalHandler(signo, &saved_sigfpe_sa);
// Also remove the node-report user signal handler, if set
if (nodereport_events & NR_SIGNAL) {
RestoreSignalHandler(nodereport_signal, &saved_user_sa);
}
if ((info->si_pid == 0) || (info->si_pid == getpid())) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd drop the superfluous parens. If you make a typo and write info->si_pid = 0 instead, then gcc won't complain because it thinks the assignment is intentional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way of policing that typo is to put the constant on the LHS, eg if (0 == info->si_pid), but I prefer readability.

// Real crash, or a signal raised internally by this process
TriggerNodeReport(Isolate::GetCurrent(), kCrashSignal, node::signo_string(signo),
__func__, nullptr, MaybeLocal<Value>());
} else {
// External crash signal sent by some other process
TriggerNodeReport(Isolate::GetCurrent(), kKillSignal, node::signo_string(signo),
__func__, nullptr, MaybeLocal<Value>());
}
// Propagate the signal
raise(signo);
}

// Utility function to start a watchdog thread - used for processing signals
static int StartWatchdogThread(void* (*thread_main)(void* unused)) {
pthread_attr_t attr;
Expand Down Expand Up @@ -361,7 +403,7 @@ static void SetupSignalHandler() {
Nan::ThrowError("node-report: initialization failed, uv_async_init() returned error\n");
}
uv_unref(reinterpret_cast<uv_handle_t*>(&nodereport_trigger_async));
RegisterSignalHandler(nodereport_signal, SignalDump, &saved_sa);
RegisterSignalHandler(nodereport_signal, SignalHandler, &saved_user_sa);
signal_thread_initialised = true;
}
}
Expand Down Expand Up @@ -420,6 +462,12 @@ void Initialize(v8::Local<v8::Object> exports) {
if (nodereport_events & NR_SIGNAL) {
SetupSignalHandler();
}
// If report requested on crash signal set up crash handler
if (nodereport_events & NR_CRASH) {
RegisterSignalHandler(SIGSEGV, CrashHandler, &saved_sigsegv_sa);
RegisterSignalHandler(SIGILL, CrashHandler, &saved_sigill_sa);
RegisterSignalHandler(SIGFPE, CrashHandler, &saved_sigfpe_sa);
}
#endif

exports->Set(Nan::New("triggerReport").ToLocalChecked(),
Expand Down
9 changes: 8 additions & 1 deletion src/node_report.cc
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ unsigned int ProcessNodeReportEvents(const char* args) {
} else if (!strncmp(cursor, "apicall", sizeof("apicall") - 1)) {
event_flags |= NR_APICALL;
cursor += sizeof("apicall") - 1;
} else if (!strncmp(cursor, "crash", sizeof("crash") - 1)) {
event_flags |= NR_CRASH;
cursor += sizeof("crash") - 1;
} else {
std::cerr << "Unrecognised argument for node-report events option: " << cursor << "\n";
return 0;
Expand Down Expand Up @@ -460,7 +463,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, c
}
return;
} else {
std::cerr << "\nWriting Node.js report to file: " << filename << "\n";
std::cerr << "Writing Node.js report to file: " << filename << "\n";
}
}

Expand Down Expand Up @@ -993,9 +996,13 @@ static void PrintJavaScriptStack(std::ostream& out, Isolate* isolate, DumpEvent
break;
case kSignal_JS:
case kSignal_UV:
case kCrashSignal:
// Print the stack using StackTrace::StackTrace() and GetStackSample() APIs
PrintStackFromStackTrace(out, isolate, event);
break;
case kKillSignal:
out << "Crash signal received from external process, no stack trace available\n";
break;
} // end switch(event)
#endif
}
Expand Down
3 changes: 2 additions & 1 deletion src/node_report.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ using v8::MaybeLocal;
#define NR_FATALERROR 0x02
#define NR_SIGNAL 0x04
#define NR_APICALL 0x08
#define NR_CRASH 0x10

// Maximum file and path name lengths
#define NR_MAXNAME 64
#define NR_MAXPATH 1024

enum DumpEvent {kException, kFatalError, kSignal_JS, kSignal_UV, kJavaScript};
enum DumpEvent {kException, kFatalError, kSignal_JS, kSignal_UV, kJavaScript, kCrashSignal, kKillSignal};

void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name, v8::MaybeLocal<v8::Value> error);
void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, v8::MaybeLocal<v8::Value> error, std::ostream& out);
Expand Down
50 changes: 50 additions & 0 deletions test/test-sigfpe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

// Testcase to produce report on native crash (sigfpe)

// This testcase uses process.kill() to raise the sigfpe signal rather than
// producing a real arithmetic exception (which would require native code). The
// code exercised in node-report is the same.

if (process.argv[2] === 'child') {
// Child process implementation
require('../');
process.kill(process.pid, 'SIGFPE');

} else {
// Parent process implementation
const common = require('./common.js');
const spawn = require('child_process').spawn;
const tap = require('tap');
const fs = require('fs');

if (common.isWindows()) {
tap.fail('Native crash support not available on Windows', { skip: true });
return;
}

const child = spawn(process.execPath, [__filename, 'child']);

// Capture stderr output from the child process
var stderr = '';
child.stderr.on('data', (chunk) => {stderr += chunk;});

// Validation on child exit
child.on('exit', (code) => {
tap.plan(4);
tap.notEqual(code, 1, 'Check for expected non-zero exit code');
const reports = common.findReports(child.pid);
tap.equal(reports.length, 1, 'Found reports ' + reports);
const report = reports[0];

// Testcase-specific report validation
fs.readFile(report, (err, data) => {
const headerSection = common.getSection(data, 'Node Report');
tap.match(headerSection, /SIGFPE/,
'Checking that header section contains crash signal name');
});
// Common report validation
const options = {pid: child.pid, commandline: child.spawnargs.join(' ')};
common.validate(tap, report, options);
});
}
50 changes: 50 additions & 0 deletions test/test-sigill.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

// Testcase to produce report on native crash (sigill)

// This testcase uses process.kill() to raise the sigill signal rather than
// executing a real illegal instruction (which would require native code). The
// code exercised in node-report is the same.

if (process.argv[2] === 'child') {
// Child process implementation
require('../');
process.kill(process.pid, 'SIGILL');

} else {
// Parent process implementation
const common = require('./common.js');
const spawn = require('child_process').spawn;
const tap = require('tap');
const fs = require('fs');

if (common.isWindows()) {
tap.fail('Native crash support not available on Windows', { skip: true });
return;
}

const child = spawn(process.execPath, [__filename, 'child']);

// Capture stderr output from the child process
var stderr = '';
child.stderr.on('data', (chunk) => {stderr += chunk;});

// Validation on child exit
child.on('exit', (code) => {
tap.plan(4);
tap.notEqual(code, 1, 'Check for expected non-zero exit code');
const reports = common.findReports(child.pid);
tap.equal(reports.length, 1, 'Found reports ' + reports);
const report = reports[0];

// Testcase-specific report validation
fs.readFile(report, (err, data) => {
const headerSection = common.getSection(data, 'Node Report');
tap.match(headerSection, /SIGILL/,
'Checking that header section contains crash signal name');
});
// Common report validation
const options = {pid: child.pid, commandline: child.spawnargs.join(' ')};
common.validate(tap, report, options);
});
}
Loading