report: expose report public native apis
Allows APM vendors to generate a diagnostic report without calling into JavaScript. Like, from their own message channels interrupting the isolate and generating a report on demand. PR-URL: https://github.com/nodejs/node/pull/44255 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
This commit is contained in:
parent
d9b2d2c0dd
commit
cb15fc56d8
1
node.gyp
1
node.gyp
@ -993,6 +993,7 @@
|
|||||||
'test/cctest/test_node_api.cc',
|
'test/cctest/test_node_api.cc',
|
||||||
'test/cctest/test_per_process.cc',
|
'test/cctest/test_per_process.cc',
|
||||||
'test/cctest/test_platform.cc',
|
'test/cctest/test_platform.cc',
|
||||||
|
'test/cctest/test_report.cc',
|
||||||
'test/cctest/test_json_utils.cc',
|
'test/cctest/test_json_utils.cc',
|
||||||
'test/cctest/test_sockaddr.cc',
|
'test/cctest/test_sockaddr.cc',
|
||||||
'test/cctest/test_traced_value.cc',
|
'test/cctest/test_traced_value.cc',
|
||||||
|
30
src/node.h
30
src/node.h
@ -75,8 +75,9 @@
|
|||||||
#include "v8-platform.h" // NOLINT(build/include_order)
|
#include "v8-platform.h" // NOLINT(build/include_order)
|
||||||
#include "node_version.h" // NODE_MODULE_VERSION
|
#include "node_version.h" // NODE_MODULE_VERSION
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
// We cannot use __POSIX__ in this header because that's only defined when
|
// We cannot use __POSIX__ in this header because that's only defined when
|
||||||
// building Node.js.
|
// building Node.js.
|
||||||
@ -617,6 +618,33 @@ NODE_EXTERN v8::MaybeLocal<v8::Value> PrepareStackTraceCallback(
|
|||||||
v8::Local<v8::Value> exception,
|
v8::Local<v8::Value> exception,
|
||||||
v8::Local<v8::Array> trace);
|
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 is nullptr, no information about the JavaScript environment
|
||||||
|
// is included in the report.
|
||||||
|
// Returns the filename of the written report.
|
||||||
|
NODE_EXTERN std::string TriggerNodeReport(v8::Isolate* isolate,
|
||||||
|
const char* message,
|
||||||
|
const char* trigger,
|
||||||
|
const std::string& filename,
|
||||||
|
v8::Local<v8::Value> error);
|
||||||
|
NODE_EXTERN std::string TriggerNodeReport(Environment* env,
|
||||||
|
const char* message,
|
||||||
|
const char* trigger,
|
||||||
|
const std::string& filename,
|
||||||
|
v8::Local<v8::Value> error);
|
||||||
|
NODE_EXTERN void GetNodeReport(v8::Isolate* isolate,
|
||||||
|
const char* message,
|
||||||
|
const char* trigger,
|
||||||
|
v8::Local<v8::Value> error,
|
||||||
|
std::ostream& out);
|
||||||
|
NODE_EXTERN void GetNodeReport(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
|
// This returns the MultiIsolatePlatform used for an Environment or IsolateData
|
||||||
// instance, if one exists.
|
// instance, if one exists.
|
||||||
NODE_EXTERN MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env);
|
NODE_EXTERN MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env);
|
||||||
|
@ -449,8 +449,7 @@ static void ReportFatalException(Environment* env,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (env->isolate_data()->options()->report_uncaught_exception) {
|
if (env->isolate_data()->options()->report_uncaught_exception) {
|
||||||
report::TriggerNodeReport(
|
TriggerNodeReport(env, report_message.c_str(), "Exception", "", error);
|
||||||
isolate, env, report_message.c_str(), "Exception", "", error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (env->options()->trace_uncaught) {
|
if (env->options()->trace_uncaught) {
|
||||||
@ -482,10 +481,6 @@ void OnFatalError(const char* location, const char* message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Isolate* isolate = Isolate::TryGetCurrent();
|
Isolate* isolate = Isolate::TryGetCurrent();
|
||||||
Environment* env = nullptr;
|
|
||||||
if (isolate != nullptr) {
|
|
||||||
env = Environment::GetCurrent(isolate);
|
|
||||||
}
|
|
||||||
bool report_on_fatalerror;
|
bool report_on_fatalerror;
|
||||||
{
|
{
|
||||||
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
|
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
|
||||||
@ -493,8 +488,7 @@ void OnFatalError(const char* location, const char* message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (report_on_fatalerror) {
|
if (report_on_fatalerror) {
|
||||||
report::TriggerNodeReport(
|
TriggerNodeReport(isolate, message, "FatalError", "", Local<Object>());
|
||||||
isolate, env, message, "FatalError", "", Local<Object>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
@ -512,10 +506,6 @@ void OOMErrorHandler(const char* location, bool is_heap_oom) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Isolate* isolate = Isolate::TryGetCurrent();
|
Isolate* isolate = Isolate::TryGetCurrent();
|
||||||
Environment* env = nullptr;
|
|
||||||
if (isolate != nullptr) {
|
|
||||||
env = Environment::GetCurrent(isolate);
|
|
||||||
}
|
|
||||||
bool report_on_fatalerror;
|
bool report_on_fatalerror;
|
||||||
{
|
{
|
||||||
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
|
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
|
||||||
@ -523,8 +513,7 @@ void OOMErrorHandler(const char* location, bool is_heap_oom) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (report_on_fatalerror) {
|
if (report_on_fatalerror) {
|
||||||
report::TriggerNodeReport(
|
TriggerNodeReport(isolate, message, "OOMError", "", Local<Object>());
|
||||||
isolate, env, message, "OOMError", "", Local<Object>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
#include "env-inl.h"
|
|
||||||
#include "json_utils.h"
|
|
||||||
#include "node_report.h"
|
#include "node_report.h"
|
||||||
#include "debug_utils-inl.h"
|
#include "debug_utils-inl.h"
|
||||||
#include "diagnosticfilename-inl.h"
|
#include "diagnosticfilename-inl.h"
|
||||||
|
#include "env-inl.h"
|
||||||
|
#include "json_utils.h"
|
||||||
#include "node_internals.h"
|
#include "node_internals.h"
|
||||||
#include "node_metadata.h"
|
#include "node_metadata.h"
|
||||||
#include "node_mutex.h"
|
#include "node_mutex.h"
|
||||||
@ -29,8 +29,6 @@ constexpr double SEC_PER_MICROS = 1e-6;
|
|||||||
constexpr int MAX_FRAME_COUNT = 10;
|
constexpr int MAX_FRAME_COUNT = 10;
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
namespace report {
|
|
||||||
|
|
||||||
using node::worker::Worker;
|
using node::worker::Worker;
|
||||||
using v8::Array;
|
using v8::Array;
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
@ -53,6 +51,7 @@ using v8::TryCatch;
|
|||||||
using v8::V8;
|
using v8::V8;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
|
namespace report {
|
||||||
// Internal/static function declarations
|
// Internal/static function declarations
|
||||||
static void WriteNodeReport(Isolate* isolate,
|
static void WriteNodeReport(Isolate* isolate,
|
||||||
Environment* env,
|
Environment* env,
|
||||||
@ -83,102 +82,6 @@ static void PrintRelease(JSONWriter* writer);
|
|||||||
static void PrintCpuInfo(JSONWriter* writer);
|
static void PrintCpuInfo(JSONWriter* writer);
|
||||||
static void PrintNetworkInterfaceInfo(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
|
// Internal function to coordinate and write the various
|
||||||
// sections of the report to the supplied stream
|
// sections of the report to the supplied stream
|
||||||
static void WriteNodeReport(Isolate* isolate,
|
static void WriteNodeReport(Isolate* isolate,
|
||||||
@ -319,12 +222,8 @@ static void WriteNodeReport(Isolate* isolate,
|
|||||||
expected_results += w->RequestInterrupt([&](Environment* env) {
|
expected_results += w->RequestInterrupt([&](Environment* env) {
|
||||||
std::ostringstream os;
|
std::ostringstream os;
|
||||||
|
|
||||||
GetNodeReport(env->isolate(),
|
GetNodeReport(
|
||||||
env,
|
env, "Worker thread subreport", trigger, Local<Value>(), os);
|
||||||
"Worker thread subreport",
|
|
||||||
trigger,
|
|
||||||
Local<Value>(),
|
|
||||||
os);
|
|
||||||
|
|
||||||
Mutex::ScopedLock lock(workers_mutex);
|
Mutex::ScopedLock lock(workers_mutex);
|
||||||
worker_infos.emplace_back(os.str());
|
worker_infos.emplace_back(os.str());
|
||||||
@ -884,4 +783,136 @@ static void PrintRelease(JSONWriter* writer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace report
|
} // namespace report
|
||||||
|
|
||||||
|
// External function to trigger a report, writing to file.
|
||||||
|
std::string TriggerNodeReport(Isolate* isolate,
|
||||||
|
const char* message,
|
||||||
|
const char* trigger,
|
||||||
|
const std::string& name,
|
||||||
|
Local<Value> error) {
|
||||||
|
Environment* env = nullptr;
|
||||||
|
if (isolate != nullptr) {
|
||||||
|
env = Environment::GetCurrent(isolate);
|
||||||
|
}
|
||||||
|
return TriggerNodeReport(env, message, trigger, name, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// External function to trigger a report, writing to file.
|
||||||
|
std::string TriggerNodeReport(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Isolate* isolate = nullptr;
|
||||||
|
if (env != nullptr) {
|
||||||
|
isolate = env->isolate();
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
const char* message,
|
||||||
|
const char* trigger,
|
||||||
|
Local<Value> error,
|
||||||
|
std::ostream& out) {
|
||||||
|
Environment* env = nullptr;
|
||||||
|
if (isolate != nullptr) {
|
||||||
|
env = Environment::GetCurrent(isolate);
|
||||||
|
}
|
||||||
|
report::WriteNodeReport(
|
||||||
|
isolate, env, message, trigger, "", out, error, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// External function to trigger a report, writing to a supplied stream.
|
||||||
|
void GetNodeReport(Environment* env,
|
||||||
|
const char* message,
|
||||||
|
const char* trigger,
|
||||||
|
Local<Value> error,
|
||||||
|
std::ostream& out) {
|
||||||
|
Isolate* isolate = nullptr;
|
||||||
|
if (env != nullptr) {
|
||||||
|
isolate = env->isolate();
|
||||||
|
}
|
||||||
|
report::WriteNodeReport(
|
||||||
|
isolate, env, message, trigger, "", out, error, false);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
@ -14,24 +14,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
namespace report {
|
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
|
// Function declarations - utility functions in src/node_report_utils.cc
|
||||||
void WalkHandle(uv_handle_t* h, void* arg);
|
void WalkHandle(uv_handle_t* h, void* arg);
|
||||||
|
|
||||||
|
@ -44,8 +44,7 @@ void WriteReport(const FunctionCallbackInfo<Value>& info) {
|
|||||||
else
|
else
|
||||||
error = Local<Value>();
|
error = Local<Value>();
|
||||||
|
|
||||||
filename = TriggerNodeReport(
|
filename = TriggerNodeReport(env, *message, *trigger, filename, error);
|
||||||
isolate, env, *message, *trigger, filename, error);
|
|
||||||
// Return value is the report filename
|
// Return value is the report filename
|
||||||
info.GetReturnValue().Set(
|
info.GetReturnValue().Set(
|
||||||
String::NewFromUtf8(isolate, filename.c_str()).ToLocalChecked());
|
String::NewFromUtf8(isolate, filename.c_str()).ToLocalChecked());
|
||||||
@ -65,8 +64,7 @@ void GetReport(const FunctionCallbackInfo<Value>& info) {
|
|||||||
else
|
else
|
||||||
error = Local<Object>();
|
error = Local<Object>();
|
||||||
|
|
||||||
GetNodeReport(
|
GetNodeReport(env, "JavaScript API", __func__, error, out);
|
||||||
isolate, env, "JavaScript API", __func__, error, out);
|
|
||||||
|
|
||||||
// Return value is the contents of a report as a string.
|
// Return value is the contents of a report as a string.
|
||||||
info.GetReturnValue().Set(
|
info.GetReturnValue().Set(
|
||||||
|
53
test/addons/report-api/binding.cc
Normal file
53
test/addons/report-api/binding.cc
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#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, "FooMessage", "BarTrigger", std::string(), Local<Value>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriggerReportNoIsolate(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
node::TriggerNodeReport(static_cast<Isolate*>(nullptr),
|
||||||
|
"FooMessage",
|
||||||
|
"BarTrigger",
|
||||||
|
std::string(),
|
||||||
|
Local<Value>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriggerReportEnv(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
|
||||||
|
node::TriggerNodeReport(
|
||||||
|
node::GetCurrentEnvironment(isolate->GetCurrentContext()),
|
||||||
|
"FooMessage",
|
||||||
|
"BarTrigger",
|
||||||
|
std::string(),
|
||||||
|
Local<Value>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriggerReportNoEnv(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
|
||||||
|
node::TriggerNodeReport(static_cast<node::Environment*>(nullptr),
|
||||||
|
"FooMessage",
|
||||||
|
"BarTrigger",
|
||||||
|
std::string(),
|
||||||
|
Local<Value>());
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(Local<Object> exports) {
|
||||||
|
NODE_SET_METHOD(exports, "triggerReport", TriggerReport);
|
||||||
|
NODE_SET_METHOD(exports, "triggerReportNoIsolate", TriggerReportNoIsolate);
|
||||||
|
NODE_SET_METHOD(exports, "triggerReportEnv", TriggerReportEnv);
|
||||||
|
NODE_SET_METHOD(exports, "triggerReportNoEnv", TriggerReportNoEnv);
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
|
9
test/addons/report-api/binding.gyp
Normal file
9
test/addons/report-api/binding.gyp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name': 'binding',
|
||||||
|
'sources': [ 'binding.cc' ],
|
||||||
|
'includes': ['../common.gypi'],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
44
test/addons/report-api/test.js
Normal file
44
test/addons/report-api/test.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const path = require('path');
|
||||||
|
const helper = require('../../common/report.js');
|
||||||
|
const tmpdir = require('../../common/tmpdir');
|
||||||
|
|
||||||
|
const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`);
|
||||||
|
const addon = require(binding);
|
||||||
|
|
||||||
|
function myAddonMain(method, hasJavaScriptFrames) {
|
||||||
|
tmpdir.refresh();
|
||||||
|
process.report.directory = tmpdir.path;
|
||||||
|
|
||||||
|
addon[method]();
|
||||||
|
|
||||||
|
const reports = helper.findReports(process.pid, tmpdir.path);
|
||||||
|
assert.strictEqual(reports.length, 1);
|
||||||
|
|
||||||
|
const report = reports[0];
|
||||||
|
helper.validate(report);
|
||||||
|
|
||||||
|
const content = require(report);
|
||||||
|
assert.strictEqual(content.header.event, 'FooMessage');
|
||||||
|
assert.strictEqual(content.header.trigger, 'BarTrigger');
|
||||||
|
|
||||||
|
// Check that the javascript stack is present.
|
||||||
|
if (hasJavaScriptFrames) {
|
||||||
|
assert.strictEqual(content.javascriptStack.stack.findIndex((frame) => frame.match('myAddonMain')), 0);
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(content.javascriptStack, undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const methods = [
|
||||||
|
['triggerReport', true],
|
||||||
|
['triggerReportNoIsolate', false],
|
||||||
|
['triggerReportEnv', true],
|
||||||
|
['triggerReportNoEnv', false],
|
||||||
|
];
|
||||||
|
for (const [method, hasJavaScriptFrames] of methods) {
|
||||||
|
myAddonMain(method, hasJavaScriptFrames);
|
||||||
|
}
|
125
test/cctest/test_report.cc
Normal file
125
test/cctest/test_report.cc
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
#include "node.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "node_test_fixture.h"
|
||||||
|
|
||||||
|
using node::Environment;
|
||||||
|
using v8::Context;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::FunctionCallbackInfo;
|
||||||
|
using v8::HandleScope;
|
||||||
|
using v8::Isolate;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::SealHandleScope;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Value;
|
||||||
|
|
||||||
|
bool report_callback_called = false;
|
||||||
|
|
||||||
|
class ReportTest : public EnvironmentTestFixture {
|
||||||
|
private:
|
||||||
|
void TearDown() override {
|
||||||
|
NodeTestFixture::TearDown();
|
||||||
|
report_callback_called = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(ReportTest, ReportWithNoIsolate) {
|
||||||
|
SealHandleScope handle_scope(isolate_);
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
node::GetNodeReport(static_cast<Isolate*>(nullptr),
|
||||||
|
"FooMessage",
|
||||||
|
"BarTrigger",
|
||||||
|
Local<Value>(),
|
||||||
|
oss);
|
||||||
|
|
||||||
|
// Simple checks on the output string contains the message and trigger.
|
||||||
|
std::string actual = oss.str();
|
||||||
|
EXPECT_NE(actual.find("FooMessage"), std::string::npos);
|
||||||
|
EXPECT_NE(actual.find("BarTrigger"), std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ReportTest, ReportWithNoEnv) {
|
||||||
|
SealHandleScope handle_scope(isolate_);
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
node::GetNodeReport(static_cast<Environment*>(nullptr),
|
||||||
|
"FooMessage",
|
||||||
|
"BarTrigger",
|
||||||
|
Local<Value>(),
|
||||||
|
oss);
|
||||||
|
|
||||||
|
// Simple checks on the output string contains the message and trigger.
|
||||||
|
std::string actual = oss.str();
|
||||||
|
EXPECT_NE(actual.find("FooMessage"), std::string::npos);
|
||||||
|
EXPECT_NE(actual.find("BarTrigger"), std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ReportTest, ReportWithIsolate) {
|
||||||
|
const HandleScope handle_scope(isolate_);
|
||||||
|
const Argv argv;
|
||||||
|
Env env{handle_scope, argv};
|
||||||
|
|
||||||
|
Local<Context> context = isolate_->GetCurrentContext();
|
||||||
|
Local<Function> fn =
|
||||||
|
Function::New(context, [](const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
node::GetNodeReport(isolate, "FooMessage", "BarTrigger", args[0], oss);
|
||||||
|
|
||||||
|
// Simple checks on the output string contains the message and trigger.
|
||||||
|
std::string actual = oss.str();
|
||||||
|
EXPECT_NE(actual.find("FooMessage"), std::string::npos);
|
||||||
|
EXPECT_NE(actual.find("BarTrigger"), std::string::npos);
|
||||||
|
|
||||||
|
report_callback_called = true;
|
||||||
|
}).ToLocalChecked();
|
||||||
|
|
||||||
|
context->Global()
|
||||||
|
->Set(context, String::NewFromUtf8(isolate_, "foo").ToLocalChecked(), fn)
|
||||||
|
.FromJust();
|
||||||
|
|
||||||
|
node::LoadEnvironment(*env, "foo()").ToLocalChecked();
|
||||||
|
|
||||||
|
EXPECT_TRUE(report_callback_called);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ReportTest, ReportWithEnv) {
|
||||||
|
const HandleScope handle_scope(isolate_);
|
||||||
|
const Argv argv;
|
||||||
|
Env env{handle_scope, argv};
|
||||||
|
|
||||||
|
Local<Context> context = isolate_->GetCurrentContext();
|
||||||
|
Local<Function> fn =
|
||||||
|
Function::New(context, [](const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
node::GetNodeReport(
|
||||||
|
node::GetCurrentEnvironment(isolate->GetCurrentContext()),
|
||||||
|
"FooMessage",
|
||||||
|
"BarTrigger",
|
||||||
|
args[0],
|
||||||
|
oss);
|
||||||
|
|
||||||
|
// Simple checks on the output string contains the message and trigger.
|
||||||
|
std::string actual = oss.str();
|
||||||
|
EXPECT_NE(actual.find("FooMessage"), std::string::npos);
|
||||||
|
EXPECT_NE(actual.find("BarTrigger"), std::string::npos);
|
||||||
|
|
||||||
|
report_callback_called = true;
|
||||||
|
}).ToLocalChecked();
|
||||||
|
|
||||||
|
context->Global()
|
||||||
|
->Set(context, String::NewFromUtf8(isolate_, "foo").ToLocalChecked(), fn)
|
||||||
|
.FromJust();
|
||||||
|
|
||||||
|
node::LoadEnvironment(*env, "foo()").ToLocalChecked();
|
||||||
|
|
||||||
|
EXPECT_TRUE(report_callback_called);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user