Skip to content

Commit

Permalink
report: expose report public native apis
Browse files Browse the repository at this point in the history
Exposing the report APIs to the add-on can be helpful for the add-on to
generate a diagnostic report when they are in a state of panic and can
not recover. Also, it allows APM vendors to generate a diagnostic report
without calling into JavaScript.
  • Loading branch information
legendecas committed Aug 16, 2022
1 parent 2b9d90d commit 17f65f5
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 119 deletions.
1 change: 1 addition & 0 deletions node.gyp
Expand Up @@ -992,6 +992,7 @@
'test/cctest/test_node_api.cc',
'test/cctest/test_per_process.cc',
'test/cctest/test_platform.cc',
'test/cctest/test_report.cc',
'test/cctest/test_json_utils.cc',
'test/cctest/test_sockaddr.cc',
'test/cctest/test_traced_value.cc',
Expand Down
18 changes: 18 additions & 0 deletions src/node.h
Expand Up @@ -617,6 +617,24 @@ NODE_EXTERN v8::MaybeLocal<v8::Value> PrepareStackTraceCallback(
v8::Local<v8::Value> exception,
v8::Local<v8::Array> trace);

// Writes a diagnostic report to a file. If filename is not provided, the
// default filename includes the date, time, PID, and a sequence number.
// The report's JavaScript stack trace is taken from err, if present.
// If isolate or env is nullptr, no information about the isolate and env
// is included in the report.
NODE_EXTERN std::string TriggerNodeReport(v8::Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
const std::string& filename,
v8::Local<v8::Value> error);
NODE_EXTERN void GetNodeReport(v8::Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
v8::Local<v8::Value> error,
std::ostream& out);

// This returns the MultiIsolatePlatform used for an Environment or IsolateData
// instance, if one exists.
NODE_EXTERN MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env);
Expand Down
6 changes: 2 additions & 4 deletions src/node_errors.cc
Expand Up @@ -485,8 +485,7 @@ void OnFatalError(const char* location, const char* message) {
}

if (report_on_fatalerror) {
report::TriggerNodeReport(
isolate, env, message, "FatalError", "", Local<Object>());
TriggerNodeReport(isolate, env, message, "FatalError", "", Local<Object>());
}

fflush(stderr);
Expand Down Expand Up @@ -515,8 +514,7 @@ void OOMErrorHandler(const char* location, bool is_heap_oom) {
}

if (report_on_fatalerror) {
report::TriggerNodeReport(
isolate, env, message, "OOMError", "", Local<Object>());
TriggerNodeReport(isolate, env, message, "OOMError", "", Local<Object>());
}

fflush(stderr);
Expand Down
201 changes: 101 additions & 100 deletions src/node_report.cc
@@ -1,8 +1,8 @@
#include "env-inl.h"
#include "json_utils.h"
#include "node_report.h"
#include "debug_utils-inl.h"
#include "diagnosticfilename-inl.h"
#include "env-inl.h"
#include "json_utils.h"
#include "node_internals.h"
#include "node_metadata.h"
#include "node_mutex.h"
Expand All @@ -29,8 +29,6 @@ constexpr double SEC_PER_MICROS = 1e-6;
constexpr int MAX_FRAME_COUNT = 10;

namespace node {
namespace report {

using node::worker::Worker;
using v8::Array;
using v8::Context;
Expand All @@ -53,6 +51,7 @@ using v8::TryCatch;
using v8::V8;
using v8::Value;

namespace report {
// Internal/static function declarations
static void WriteNodeReport(Isolate* isolate,
Environment* env,
Expand Down Expand Up @@ -83,102 +82,6 @@ static void PrintRelease(JSONWriter* writer);
static void PrintCpuInfo(JSONWriter* writer);
static void PrintNetworkInterfaceInfo(JSONWriter* writer);

// External function to trigger a report, writing to file.
std::string TriggerNodeReport(Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
const std::string& name,
Local<Value> error) {
std::string filename;

// Determine the required report filename. In order of priority:
// 1) supplied on API 2) configured on startup 3) default generated
if (!name.empty()) {
// Filename was specified as API parameter.
filename = name;
} else {
std::string report_filename;
{
Mutex::ScopedLock lock(per_process::cli_options_mutex);
report_filename = per_process::cli_options->report_filename;
}
if (report_filename.length() > 0) {
// File name was supplied via start-up option.
filename = report_filename;
} else {
filename = *DiagnosticFilename(env != nullptr ? env->thread_id() : 0,
"report", "json");
}
}

// Open the report file stream for writing. Supports stdout/err,
// user-specified or (default) generated name
std::ofstream outfile;
std::ostream* outstream;
if (filename == "stdout") {
outstream = &std::cout;
} else if (filename == "stderr") {
outstream = &std::cerr;
} else {
std::string report_directory;
{
Mutex::ScopedLock lock(per_process::cli_options_mutex);
report_directory = per_process::cli_options->report_directory;
}
// Regular file. Append filename to directory path if one was specified
if (report_directory.length() > 0) {
std::string pathname = report_directory;
pathname += kPathSeparator;
pathname += filename;
outfile.open(pathname, std::ios::out | std::ios::binary);
} else {
outfile.open(filename, std::ios::out | std::ios::binary);
}
// Check for errors on the file open
if (!outfile.is_open()) {
std::cerr << "\nFailed to open Node.js report file: " << filename;

if (report_directory.length() > 0)
std::cerr << " directory: " << report_directory;

std::cerr << " (errno: " << errno << ")" << std::endl;
return "";
}
outstream = &outfile;
std::cerr << "\nWriting Node.js report to file: " << filename;
}

bool compact;
{
Mutex::ScopedLock lock(per_process::cli_options_mutex);
compact = per_process::cli_options->report_compact;
}
WriteNodeReport(isolate, env, message, trigger, filename, *outstream,
error, compact);

// Do not close stdout/stderr, only close files we opened.
if (outfile.is_open()) {
outfile.close();
}

// Do not mix JSON and free-form text on stderr.
if (filename != "stderr") {
std::cerr << "\nNode.js report completed" << std::endl;
}
return filename;
}

// External function to trigger a report, writing to a supplied stream.
void GetNodeReport(Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
Local<Value> error,
std::ostream& out) {
WriteNodeReport(isolate, env, message, trigger, "", out, error, false);
}

// Internal function to coordinate and write the various
// sections of the report to the supplied stream
static void WriteNodeReport(Isolate* isolate,
Expand Down Expand Up @@ -884,4 +787,102 @@ static void PrintRelease(JSONWriter* writer) {
}

} // namespace report

// External function to trigger a report, writing to file.
std::string TriggerNodeReport(Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
const std::string& name,
Local<Value> error) {
std::string filename;

// Determine the required report filename. In order of priority:
// 1) supplied on API 2) configured on startup 3) default generated
if (!name.empty()) {
// Filename was specified as API parameter.
filename = name;
} else {
std::string report_filename;
{
Mutex::ScopedLock lock(per_process::cli_options_mutex);
report_filename = per_process::cli_options->report_filename;
}
if (report_filename.length() > 0) {
// File name was supplied via start-up option.
filename = report_filename;
} else {
filename = *DiagnosticFilename(
env != nullptr ? env->thread_id() : 0, "report", "json");
}
}

// Open the report file stream for writing. Supports stdout/err,
// user-specified or (default) generated name
std::ofstream outfile;
std::ostream* outstream;
if (filename == "stdout") {
outstream = &std::cout;
} else if (filename == "stderr") {
outstream = &std::cerr;
} else {
std::string report_directory;
{
Mutex::ScopedLock lock(per_process::cli_options_mutex);
report_directory = per_process::cli_options->report_directory;
}
// Regular file. Append filename to directory path if one was specified
if (report_directory.length() > 0) {
std::string pathname = report_directory;
pathname += kPathSeparator;
pathname += filename;
outfile.open(pathname, std::ios::out | std::ios::binary);
} else {
outfile.open(filename, std::ios::out | std::ios::binary);
}
// Check for errors on the file open
if (!outfile.is_open()) {
std::cerr << "\nFailed to open Node.js report file: " << filename;

if (report_directory.length() > 0)
std::cerr << " directory: " << report_directory;

std::cerr << " (errno: " << errno << ")" << std::endl;
return "";
}
outstream = &outfile;
std::cerr << "\nWriting Node.js report to file: " << filename;
}

bool compact;
{
Mutex::ScopedLock lock(per_process::cli_options_mutex);
compact = per_process::cli_options->report_compact;
}
report::WriteNodeReport(
isolate, env, message, trigger, filename, *outstream, error, compact);

// Do not close stdout/stderr, only close files we opened.
if (outfile.is_open()) {
outfile.close();
}

// Do not mix JSON and free-form text on stderr.
if (filename != "stderr") {
std::cerr << "\nNode.js report completed" << std::endl;
}
return filename;
}

// External function to trigger a report, writing to a supplied stream.
void GetNodeReport(Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
Local<Value> error,
std::ostream& out) {
report::WriteNodeReport(
isolate, env, message, trigger, "", out, error, false);
}

} // namespace node
31 changes: 16 additions & 15 deletions src/node_report.h
Expand Up @@ -13,25 +13,11 @@
#include <unistd.h>
#endif

#include <sstream>
#include <iomanip>

namespace node {
namespace report {

// Function declarations - functions in src/node_report.cc
std::string TriggerNodeReport(v8::Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
const std::string& name,
v8::Local<v8::Value> error);
void GetNodeReport(v8::Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
v8::Local<v8::Value> error,
std::ostream& out);

// Function declarations - utility functions in src/node_report_utils.cc
void WalkHandle(uv_handle_t* h, void* arg);

Expand All @@ -49,6 +35,21 @@ void WriteReport(const v8::FunctionCallbackInfo<v8::Value>& info);
void GetReport(const v8::FunctionCallbackInfo<v8::Value>& info);

} // namespace report

// Function declarations - functions in src/node_report.cc
std::string TriggerNodeReport(v8::Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
const std::string& name,
v8::Local<v8::Value> error);
void GetNodeReport(v8::Isolate* isolate,
Environment* env,
const char* message,
const char* trigger,
v8::Local<v8::Value> error,
std::ostream& out);

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Expand Down
26 changes: 26 additions & 0 deletions test/addons/report-api/binding.cc
@@ -0,0 +1,26 @@
#include <node.h>
#include <v8.h>

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::Value;

void TriggerReport(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();

node::TriggerNodeReport(
isolate,
node::GetCurrentEnvironment(isolate->GetCurrentContext()),
"FooMessage",
"BarTrigger",
std::string(),
Local<Value>());
}

void init(Local<Object> exports) {
NODE_SET_METHOD(exports, "triggerReport", TriggerReport);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, init)
9 changes: 9 additions & 0 deletions test/addons/report-api/binding.gyp
@@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.cc' ],
'includes': ['../common.gypi'],
}
]
}

0 comments on commit 17f65f5

Please sign in to comment.