Skip to content

Commit 8c148bf

Browse files
committed
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.
1 parent 2b9d90d commit 8c148bf

File tree

9 files changed

+278
-119
lines changed

9 files changed

+278
-119
lines changed

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,7 @@
992992
'test/cctest/test_node_api.cc',
993993
'test/cctest/test_per_process.cc',
994994
'test/cctest/test_platform.cc',
995+
'test/cctest/test_report.cc',
995996
'test/cctest/test_json_utils.cc',
996997
'test/cctest/test_sockaddr.cc',
997998
'test/cctest/test_traced_value.cc',

src/node.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,24 @@ NODE_EXTERN v8::MaybeLocal<v8::Value> PrepareStackTraceCallback(
617617
v8::Local<v8::Value> exception,
618618
v8::Local<v8::Array> trace);
619619

620+
// Writes a diagnostic report to a file. If filename is not provided, the
621+
// default filename includes the date, time, PID, and a sequence number.
622+
// The report's JavaScript stack trace is taken from err, if present.
623+
// If isolate or env is nullptr, no information about the isolate and env
624+
// is included in the report.
625+
NODE_EXTERN std::string TriggerNodeReport(v8::Isolate* isolate,
626+
Environment* env,
627+
const char* message,
628+
const char* trigger,
629+
const std::string& filename,
630+
v8::Local<v8::Value> error);
631+
NODE_EXTERN void GetNodeReport(v8::Isolate* isolate,
632+
Environment* env,
633+
const char* message,
634+
const char* trigger,
635+
v8::Local<v8::Value> error,
636+
std::ostream& out);
637+
620638
// This returns the MultiIsolatePlatform used for an Environment or IsolateData
621639
// instance, if one exists.
622640
NODE_EXTERN MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env);

src/node_errors.cc

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,7 @@ void OnFatalError(const char* location, const char* message) {
485485
}
486486

487487
if (report_on_fatalerror) {
488-
report::TriggerNodeReport(
489-
isolate, env, message, "FatalError", "", Local<Object>());
488+
TriggerNodeReport(isolate, env, message, "FatalError", "", Local<Object>());
490489
}
491490

492491
fflush(stderr);
@@ -515,8 +514,7 @@ void OOMErrorHandler(const char* location, bool is_heap_oom) {
515514
}
516515

517516
if (report_on_fatalerror) {
518-
report::TriggerNodeReport(
519-
isolate, env, message, "OOMError", "", Local<Object>());
517+
TriggerNodeReport(isolate, env, message, "OOMError", "", Local<Object>());
520518
}
521519

522520
fflush(stderr);

src/node_report.cc

Lines changed: 101 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#include "env-inl.h"
2-
#include "json_utils.h"
31
#include "node_report.h"
42
#include "debug_utils-inl.h"
53
#include "diagnosticfilename-inl.h"
4+
#include "env-inl.h"
5+
#include "json_utils.h"
66
#include "node_internals.h"
77
#include "node_metadata.h"
88
#include "node_mutex.h"
@@ -29,8 +29,6 @@ constexpr double SEC_PER_MICROS = 1e-6;
2929
constexpr int MAX_FRAME_COUNT = 10;
3030

3131
namespace node {
32-
namespace report {
33-
3432
using node::worker::Worker;
3533
using v8::Array;
3634
using v8::Context;
@@ -53,6 +51,7 @@ using v8::TryCatch;
5351
using v8::V8;
5452
using v8::Value;
5553

54+
namespace report {
5655
// Internal/static function declarations
5756
static void WriteNodeReport(Isolate* isolate,
5857
Environment* env,
@@ -83,102 +82,6 @@ static void PrintRelease(JSONWriter* writer);
8382
static void PrintCpuInfo(JSONWriter* writer);
8483
static void PrintNetworkInterfaceInfo(JSONWriter* writer);
8584

86-
// External function to trigger a report, writing to file.
87-
std::string TriggerNodeReport(Isolate* isolate,
88-
Environment* env,
89-
const char* message,
90-
const char* trigger,
91-
const std::string& name,
92-
Local<Value> error) {
93-
std::string filename;
94-
95-
// Determine the required report filename. In order of priority:
96-
// 1) supplied on API 2) configured on startup 3) default generated
97-
if (!name.empty()) {
98-
// Filename was specified as API parameter.
99-
filename = name;
100-
} else {
101-
std::string report_filename;
102-
{
103-
Mutex::ScopedLock lock(per_process::cli_options_mutex);
104-
report_filename = per_process::cli_options->report_filename;
105-
}
106-
if (report_filename.length() > 0) {
107-
// File name was supplied via start-up option.
108-
filename = report_filename;
109-
} else {
110-
filename = *DiagnosticFilename(env != nullptr ? env->thread_id() : 0,
111-
"report", "json");
112-
}
113-
}
114-
115-
// Open the report file stream for writing. Supports stdout/err,
116-
// user-specified or (default) generated name
117-
std::ofstream outfile;
118-
std::ostream* outstream;
119-
if (filename == "stdout") {
120-
outstream = &std::cout;
121-
} else if (filename == "stderr") {
122-
outstream = &std::cerr;
123-
} else {
124-
std::string report_directory;
125-
{
126-
Mutex::ScopedLock lock(per_process::cli_options_mutex);
127-
report_directory = per_process::cli_options->report_directory;
128-
}
129-
// Regular file. Append filename to directory path if one was specified
130-
if (report_directory.length() > 0) {
131-
std::string pathname = report_directory;
132-
pathname += kPathSeparator;
133-
pathname += filename;
134-
outfile.open(pathname, std::ios::out | std::ios::binary);
135-
} else {
136-
outfile.open(filename, std::ios::out | std::ios::binary);
137-
}
138-
// Check for errors on the file open
139-
if (!outfile.is_open()) {
140-
std::cerr << "\nFailed to open Node.js report file: " << filename;
141-
142-
if (report_directory.length() > 0)
143-
std::cerr << " directory: " << report_directory;
144-
145-
std::cerr << " (errno: " << errno << ")" << std::endl;
146-
return "";
147-
}
148-
outstream = &outfile;
149-
std::cerr << "\nWriting Node.js report to file: " << filename;
150-
}
151-
152-
bool compact;
153-
{
154-
Mutex::ScopedLock lock(per_process::cli_options_mutex);
155-
compact = per_process::cli_options->report_compact;
156-
}
157-
WriteNodeReport(isolate, env, message, trigger, filename, *outstream,
158-
error, compact);
159-
160-
// Do not close stdout/stderr, only close files we opened.
161-
if (outfile.is_open()) {
162-
outfile.close();
163-
}
164-
165-
// Do not mix JSON and free-form text on stderr.
166-
if (filename != "stderr") {
167-
std::cerr << "\nNode.js report completed" << std::endl;
168-
}
169-
return filename;
170-
}
171-
172-
// External function to trigger a report, writing to a supplied stream.
173-
void GetNodeReport(Isolate* isolate,
174-
Environment* env,
175-
const char* message,
176-
const char* trigger,
177-
Local<Value> error,
178-
std::ostream& out) {
179-
WriteNodeReport(isolate, env, message, trigger, "", out, error, false);
180-
}
181-
18285
// Internal function to coordinate and write the various
18386
// sections of the report to the supplied stream
18487
static void WriteNodeReport(Isolate* isolate,
@@ -884,4 +787,102 @@ static void PrintRelease(JSONWriter* writer) {
884787
}
885788

886789
} // namespace report
790+
791+
// External function to trigger a report, writing to file.
792+
std::string TriggerNodeReport(Isolate* isolate,
793+
Environment* env,
794+
const char* message,
795+
const char* trigger,
796+
const std::string& name,
797+
Local<Value> error) {
798+
std::string filename;
799+
800+
// Determine the required report filename. In order of priority:
801+
// 1) supplied on API 2) configured on startup 3) default generated
802+
if (!name.empty()) {
803+
// Filename was specified as API parameter.
804+
filename = name;
805+
} else {
806+
std::string report_filename;
807+
{
808+
Mutex::ScopedLock lock(per_process::cli_options_mutex);
809+
report_filename = per_process::cli_options->report_filename;
810+
}
811+
if (report_filename.length() > 0) {
812+
// File name was supplied via start-up option.
813+
filename = report_filename;
814+
} else {
815+
filename = *DiagnosticFilename(
816+
env != nullptr ? env->thread_id() : 0, "report", "json");
817+
}
818+
}
819+
820+
// Open the report file stream for writing. Supports stdout/err,
821+
// user-specified or (default) generated name
822+
std::ofstream outfile;
823+
std::ostream* outstream;
824+
if (filename == "stdout") {
825+
outstream = &std::cout;
826+
} else if (filename == "stderr") {
827+
outstream = &std::cerr;
828+
} else {
829+
std::string report_directory;
830+
{
831+
Mutex::ScopedLock lock(per_process::cli_options_mutex);
832+
report_directory = per_process::cli_options->report_directory;
833+
}
834+
// Regular file. Append filename to directory path if one was specified
835+
if (report_directory.length() > 0) {
836+
std::string pathname = report_directory;
837+
pathname += kPathSeparator;
838+
pathname += filename;
839+
outfile.open(pathname, std::ios::out | std::ios::binary);
840+
} else {
841+
outfile.open(filename, std::ios::out | std::ios::binary);
842+
}
843+
// Check for errors on the file open
844+
if (!outfile.is_open()) {
845+
std::cerr << "\nFailed to open Node.js report file: " << filename;
846+
847+
if (report_directory.length() > 0)
848+
std::cerr << " directory: " << report_directory;
849+
850+
std::cerr << " (errno: " << errno << ")" << std::endl;
851+
return "";
852+
}
853+
outstream = &outfile;
854+
std::cerr << "\nWriting Node.js report to file: " << filename;
855+
}
856+
857+
bool compact;
858+
{
859+
Mutex::ScopedLock lock(per_process::cli_options_mutex);
860+
compact = per_process::cli_options->report_compact;
861+
}
862+
report::WriteNodeReport(
863+
isolate, env, message, trigger, filename, *outstream, error, compact);
864+
865+
// Do not close stdout/stderr, only close files we opened.
866+
if (outfile.is_open()) {
867+
outfile.close();
868+
}
869+
870+
// Do not mix JSON and free-form text on stderr.
871+
if (filename != "stderr") {
872+
std::cerr << "\nNode.js report completed" << std::endl;
873+
}
874+
return filename;
875+
}
876+
877+
// External function to trigger a report, writing to a supplied stream.
878+
void GetNodeReport(Isolate* isolate,
879+
Environment* env,
880+
const char* message,
881+
const char* trigger,
882+
Local<Value> error,
883+
std::ostream& out) {
884+
report::WriteNodeReport(
885+
isolate, env, message, trigger, "", out, error, false);
886+
}
887+
887888
} // namespace node

src/node_report.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,10 @@
1414
#endif
1515

1616
#include <iomanip>
17+
#include <sstream>
1718

1819
namespace node {
1920
namespace report {
20-
21-
// Function declarations - functions in src/node_report.cc
22-
std::string TriggerNodeReport(v8::Isolate* isolate,
23-
Environment* env,
24-
const char* message,
25-
const char* trigger,
26-
const std::string& name,
27-
v8::Local<v8::Value> error);
28-
void GetNodeReport(v8::Isolate* isolate,
29-
Environment* env,
30-
const char* message,
31-
const char* trigger,
32-
v8::Local<v8::Value> error,
33-
std::ostream& out);
34-
3521
// Function declarations - utility functions in src/node_report_utils.cc
3622
void WalkHandle(uv_handle_t* h, void* arg);
3723

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

5137
} // namespace report
38+
39+
// Function declarations - functions in src/node_report.cc
40+
std::string TriggerNodeReport(v8::Isolate* isolate,
41+
Environment* env,
42+
const char* message,
43+
const char* trigger,
44+
const std::string& name,
45+
v8::Local<v8::Value> error);
46+
void GetNodeReport(v8::Isolate* isolate,
47+
Environment* env,
48+
const char* message,
49+
const char* trigger,
50+
v8::Local<v8::Value> error,
51+
std::ostream& out);
52+
5253
} // namespace node
5354

5455
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

test/addons/report-api/binding.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <node.h>
2+
#include <v8.h>
3+
4+
using v8::FunctionCallbackInfo;
5+
using v8::Isolate;
6+
using v8::Local;
7+
using v8::Object;
8+
using v8::Value;
9+
10+
void TriggerReport(const FunctionCallbackInfo<Value>& args) {
11+
Isolate* isolate = args.GetIsolate();
12+
13+
node::TriggerNodeReport(
14+
isolate,
15+
node::GetCurrentEnvironment(isolate->GetCurrentContext()),
16+
"FooMessage",
17+
"BarTrigger",
18+
std::string(),
19+
Local<Value>());
20+
}
21+
22+
void init(Local<Object> exports) {
23+
NODE_SET_METHOD(exports, "triggerReport", TriggerReport);
24+
}
25+
26+
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

test/addons/report-api/binding.gyp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
}
8+
]
9+
}

0 commit comments

Comments
 (0)