Skip to content

Commit 1f23c17

Browse files
legendecasRafaelGSS
authored andcommitted
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: #44255 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
1 parent df25900 commit 1f23c17

10 files changed

+404
-140
lines changed

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,7 @@
12321232
'test/cctest/test_node_api.cc',
12331233
'test/cctest/test_per_process.cc',
12341234
'test/cctest/test_platform.cc',
1235+
'test/cctest/test_report.cc',
12351236
'test/cctest/test_json_utils.cc',
12361237
'test/cctest/test_sockaddr.cc',
12371238
'test/cctest/test_traced_value.cc',

src/node.h

+29-1
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@
7575
#include "v8-platform.h" // NOLINT(build/include_order)
7676
#include "node_version.h" // NODE_MODULE_VERSION
7777

78-
#include <memory>
7978
#include <functional>
79+
#include <memory>
80+
#include <ostream>
8081

8182
// We cannot use __POSIX__ in this header because that's only defined when
8283
// building Node.js.
@@ -531,6 +532,33 @@ NODE_EXTERN v8::MaybeLocal<v8::Value> PrepareStackTraceCallback(
531532
v8::Local<v8::Value> exception,
532533
v8::Local<v8::Array> trace);
533534

535+
// Writes a diagnostic report to a file. If filename is not provided, the
536+
// default filename includes the date, time, PID, and a sequence number.
537+
// The report's JavaScript stack trace is taken from err, if present.
538+
// If isolate is nullptr, no information about the JavaScript environment
539+
// is included in the report.
540+
// Returns the filename of the written report.
541+
NODE_EXTERN std::string TriggerNodeReport(v8::Isolate* isolate,
542+
const char* message,
543+
const char* trigger,
544+
const std::string& filename,
545+
v8::Local<v8::Value> error);
546+
NODE_EXTERN std::string TriggerNodeReport(Environment* env,
547+
const char* message,
548+
const char* trigger,
549+
const std::string& filename,
550+
v8::Local<v8::Value> error);
551+
NODE_EXTERN void GetNodeReport(v8::Isolate* isolate,
552+
const char* message,
553+
const char* trigger,
554+
v8::Local<v8::Value> error,
555+
std::ostream& out);
556+
NODE_EXTERN void GetNodeReport(Environment* env,
557+
const char* message,
558+
const char* trigger,
559+
v8::Local<v8::Value> error,
560+
std::ostream& out);
561+
534562
// This returns the MultiIsolatePlatform used for an Environment or IsolateData
535563
// instance, if one exists.
536564
NODE_EXTERN MultiIsolatePlatform* GetMultiIsolatePlatform(Environment* env);

src/node_errors.cc

+3-14
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,7 @@ static void ReportFatalException(Environment* env,
449449
}
450450

451451
if (env->isolate_data()->options()->report_uncaught_exception) {
452-
report::TriggerNodeReport(
453-
isolate, env, report_message.c_str(), "Exception", "", error);
452+
TriggerNodeReport(env, report_message.c_str(), "Exception", "", error);
454453
}
455454

456455
if (env->options()->trace_uncaught) {
@@ -482,19 +481,14 @@ void OnFatalError(const char* location, const char* message) {
482481
}
483482

484483
Isolate* isolate = Isolate::TryGetCurrent();
485-
Environment* env = nullptr;
486-
if (isolate != nullptr) {
487-
env = Environment::GetCurrent(isolate);
488-
}
489484
bool report_on_fatalerror;
490485
{
491486
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
492487
report_on_fatalerror = per_process::cli_options->report_on_fatalerror;
493488
}
494489

495490
if (report_on_fatalerror) {
496-
report::TriggerNodeReport(
497-
isolate, env, message, "FatalError", "", Local<Object>());
491+
TriggerNodeReport(isolate, message, "FatalError", "", Local<Object>());
498492
}
499493

500494
fflush(stderr);
@@ -512,19 +506,14 @@ void OOMErrorHandler(const char* location, bool is_heap_oom) {
512506
}
513507

514508
Isolate* isolate = Isolate::TryGetCurrent();
515-
Environment* env = nullptr;
516-
if (isolate != nullptr) {
517-
env = Environment::GetCurrent(isolate);
518-
}
519509
bool report_on_fatalerror;
520510
{
521511
Mutex::ScopedLock lock(node::per_process::cli_options_mutex);
522512
report_on_fatalerror = per_process::cli_options->report_on_fatalerror;
523513
}
524514

525515
if (report_on_fatalerror) {
526-
report::TriggerNodeReport(
527-
isolate, env, message, "OOMError", "", Local<Object>());
516+
TriggerNodeReport(isolate, message, "OOMError", "", Local<Object>());
528517
}
529518

530519
fflush(stderr);

src/node_report.cc

+137-106
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,
@@ -319,12 +222,8 @@ static void WriteNodeReport(Isolate* isolate,
319222
expected_results += w->RequestInterrupt([&](Environment* env) {
320223
std::ostringstream os;
321224

322-
GetNodeReport(env->isolate(),
323-
env,
324-
"Worker thread subreport",
325-
trigger,
326-
Local<Value>(),
327-
os);
225+
GetNodeReport(
226+
env, "Worker thread subreport", trigger, Local<Value>(), os);
328227

329228
Mutex::ScopedLock lock(workers_mutex);
330229
worker_infos.emplace_back(os.str());
@@ -884,4 +783,136 @@ static void PrintRelease(JSONWriter* writer) {
884783
}
885784

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

0 commit comments

Comments
 (0)