/src/node/src/node_report.cc
Line | Count | Source |
1 | | #include "node_report.h" |
2 | | #include "debug_utils-inl.h" |
3 | | #include "diagnosticfilename-inl.h" |
4 | | #include "env-inl.h" |
5 | | #include "json_utils.h" |
6 | | #include "node_internals.h" |
7 | | #include "node_metadata.h" |
8 | | #include "node_mutex.h" |
9 | | #include "node_worker.h" |
10 | | #include "permission/permission.h" |
11 | | #include "util.h" |
12 | | |
13 | | #ifdef _WIN32 |
14 | | #include <Windows.h> |
15 | | #else // !_WIN32 |
16 | | #include <cxxabi.h> |
17 | | #include <sys/resource.h> |
18 | | #include <dlfcn.h> |
19 | | #endif |
20 | | |
21 | | #include <cstring> |
22 | | #include <ctime> |
23 | | #include <cwctype> |
24 | | #include <fstream> |
25 | | #include <ranges> |
26 | | |
27 | | constexpr int NODE_REPORT_VERSION = 5; |
28 | | constexpr int NANOS_PER_SEC = 1000 * 1000 * 1000; |
29 | | constexpr double SEC_PER_MICROS = 1e-6; |
30 | | constexpr int MAX_FRAME_COUNT = node::kMaxFrameCountForLogging; |
31 | | |
32 | | namespace node { |
33 | | using node::worker::Worker; |
34 | | using v8::Array; |
35 | | using v8::Context; |
36 | | using v8::HandleScope; |
37 | | using v8::HeapSpaceStatistics; |
38 | | using v8::HeapStatistics; |
39 | | using v8::Isolate; |
40 | | using v8::Just; |
41 | | using v8::Local; |
42 | | using v8::Maybe; |
43 | | using v8::MaybeLocal; |
44 | | using v8::Nothing; |
45 | | using v8::Object; |
46 | | using v8::RegisterState; |
47 | | using v8::SampleInfo; |
48 | | using v8::StackFrame; |
49 | | using v8::StackTrace; |
50 | | using v8::String; |
51 | | using v8::TryCatch; |
52 | | using v8::V8; |
53 | | using v8::Value; |
54 | | |
55 | | namespace report { |
56 | | // Internal/static function declarations |
57 | | static void WriteNodeReport(Isolate* isolate, |
58 | | Environment* env, |
59 | | std::string_view message, |
60 | | std::string_view trigger, |
61 | | std::string_view filename, |
62 | | std::ostream& out, |
63 | | Local<Value> error, |
64 | | bool compact, |
65 | | bool exclude_network = false, |
66 | | bool exclude_env = false); |
67 | | static void PrintVersionInformation(JSONWriter* writer, |
68 | | bool exclude_network = false); |
69 | | static void PrintJavaScriptErrorStack(JSONWriter* writer, |
70 | | Isolate* isolate, |
71 | | Local<Value> error, |
72 | | std::string_view trigger); |
73 | | static void PrintEmptyJavaScriptStack(JSONWriter* writer); |
74 | | static void PrintJavaScriptStack(JSONWriter* writer, |
75 | | Isolate* isolate, |
76 | | std::string_view trigger); |
77 | | static void PrintJavaScriptErrorProperties(JSONWriter* writer, |
78 | | Isolate* isolate, |
79 | | Local<Value> error); |
80 | | static void PrintNativeStack(JSONWriter* writer); |
81 | | static void PrintResourceUsage(JSONWriter* writer); |
82 | | static void PrintGCStatistics(JSONWriter* writer, Isolate* isolate); |
83 | | static void PrintEnvironmentVariables(JSONWriter* writer); |
84 | | static void PrintSystemInformation(JSONWriter* writer); |
85 | | static void PrintLoadedLibraries(JSONWriter* writer); |
86 | | static void PrintComponentVersions(JSONWriter* writer); |
87 | | static void PrintRelease(JSONWriter* writer); |
88 | | static void PrintCpuInfo(JSONWriter* writer); |
89 | | static void PrintNetworkInterfaceInfo(JSONWriter* writer); |
90 | | |
91 | | // Internal function to coordinate and write the various |
92 | | // sections of the report to the supplied stream |
93 | | static void WriteNodeReport(Isolate* isolate, |
94 | | Environment* env, |
95 | | std::string_view message, |
96 | | std::string_view trigger, |
97 | | std::string_view filename, |
98 | | std::ostream& out, |
99 | | Local<Value> error, |
100 | | bool compact, |
101 | | bool exclude_network, |
102 | 0 | bool exclude_env) { |
103 | | // Obtain the current time and the pid. |
104 | 0 | TIME_TYPE tm_struct; |
105 | 0 | DiagnosticFilename::LocalTime(&tm_struct); |
106 | 0 | uv_pid_t pid = uv_os_getpid(); |
107 | | |
108 | | // Save formatting for output stream. |
109 | 0 | std::ios old_state(nullptr); |
110 | 0 | old_state.copyfmt(out); |
111 | | |
112 | | // File stream opened OK, now start printing the report content: |
113 | | // the title and header information (event, filename, timestamp and pid) |
114 | |
|
115 | 0 | JSONWriter writer(out, compact); |
116 | 0 | writer.json_start(); |
117 | 0 | writer.json_objectstart("header"); |
118 | 0 | writer.json_keyvalue("reportVersion", NODE_REPORT_VERSION); |
119 | 0 | writer.json_keyvalue("event", message); |
120 | 0 | writer.json_keyvalue("trigger", trigger); |
121 | 0 | if (!filename.empty()) |
122 | 0 | writer.json_keyvalue("filename", filename); |
123 | 0 | else |
124 | 0 | writer.json_keyvalue("filename", JSONWriter::Null{}); |
125 | | |
126 | | // Report dump event and module load date/time stamps |
127 | 0 | char timebuf[64]; |
128 | | #ifdef _WIN32 |
129 | | snprintf(timebuf, |
130 | | sizeof(timebuf), |
131 | | "%4d-%02d-%02dT%02d:%02d:%02dZ", |
132 | | tm_struct.wYear, |
133 | | tm_struct.wMonth, |
134 | | tm_struct.wDay, |
135 | | tm_struct.wHour, |
136 | | tm_struct.wMinute, |
137 | | tm_struct.wSecond); |
138 | | writer.json_keyvalue("dumpEventTime", timebuf); |
139 | | #else // UNIX, macOS |
140 | 0 | snprintf(timebuf, |
141 | 0 | sizeof(timebuf), |
142 | 0 | "%4d-%02d-%02dT%02d:%02d:%02dZ", |
143 | 0 | tm_struct.tm_year + 1900, |
144 | 0 | tm_struct.tm_mon + 1, |
145 | 0 | tm_struct.tm_mday, |
146 | 0 | tm_struct.tm_hour, |
147 | 0 | tm_struct.tm_min, |
148 | 0 | tm_struct.tm_sec); |
149 | 0 | writer.json_keyvalue("dumpEventTime", timebuf); |
150 | 0 | #endif |
151 | |
|
152 | 0 | uv_timeval64_t ts; |
153 | 0 | if (uv_gettimeofday(&ts) == 0) { |
154 | 0 | writer.json_keyvalue("dumpEventTimeStamp", |
155 | 0 | std::to_string(ts.tv_sec * 1000 + ts.tv_usec / 1000)); |
156 | 0 | } |
157 | | |
158 | | // Report native process ID |
159 | 0 | writer.json_keyvalue("processId", pid); |
160 | 0 | if (env != nullptr) |
161 | 0 | writer.json_keyvalue("threadId", env->thread_id()); |
162 | 0 | else |
163 | 0 | writer.json_keyvalue("threadId", JSONWriter::Null{}); |
164 | |
|
165 | 0 | { |
166 | | // Report the process cwd. |
167 | 0 | char buf[PATH_MAX_BYTES]; |
168 | 0 | size_t cwd_size = sizeof(buf); |
169 | 0 | if (uv_cwd(buf, &cwd_size) == 0) |
170 | 0 | writer.json_keyvalue("cwd", buf); |
171 | 0 | } |
172 | | |
173 | | // Report out the command line. |
174 | 0 | if (!per_process::cli_options->cmdline.empty()) { |
175 | 0 | writer.json_arraystart("commandLine"); |
176 | 0 | for (const std::string& arg : per_process::cli_options->cmdline) { |
177 | 0 | writer.json_element(arg); |
178 | 0 | } |
179 | 0 | writer.json_arrayend(); |
180 | 0 | } |
181 | | |
182 | | // Report Node.js and OS version information |
183 | 0 | PrintVersionInformation(&writer, exclude_network); |
184 | 0 | writer.json_objectend(); |
185 | |
|
186 | 0 | if (isolate != nullptr) { |
187 | 0 | writer.json_objectstart("javascriptStack"); |
188 | | // Report summary JavaScript error stack backtrace |
189 | 0 | PrintJavaScriptErrorStack(&writer, isolate, error, trigger); |
190 | |
|
191 | 0 | writer.json_objectend(); // the end of 'javascriptStack' |
192 | | |
193 | | // Report V8 Heap and Garbage Collector information |
194 | 0 | PrintGCStatistics(&writer, isolate); |
195 | 0 | } else { |
196 | 0 | writer.json_objectstart("javascriptStack"); |
197 | 0 | PrintEmptyJavaScriptStack(&writer); |
198 | 0 | writer.json_objectend(); // the end of 'javascriptStack' |
199 | 0 | } |
200 | | |
201 | | // Report native stack backtrace |
202 | 0 | PrintNativeStack(&writer); |
203 | | |
204 | | // Report OS and current thread resource usage |
205 | 0 | PrintResourceUsage(&writer); |
206 | |
|
207 | 0 | writer.json_arraystart("libuv"); |
208 | 0 | if (env != nullptr) { |
209 | 0 | uv_walk(env->event_loop(), |
210 | 0 | exclude_network ? WalkHandleNoNetwork : WalkHandleNetwork, |
211 | 0 | static_cast<void*>(&writer)); |
212 | |
|
213 | 0 | writer.json_start(); |
214 | 0 | writer.json_keyvalue("type", "loop"); |
215 | 0 | writer.json_keyvalue("is_active", |
216 | 0 | static_cast<bool>(uv_loop_alive(env->event_loop()))); |
217 | 0 | writer.json_keyvalue("address", |
218 | 0 | ValueToHexString(reinterpret_cast<int64_t>(env->event_loop()))); |
219 | | |
220 | | // Report Event loop idle time |
221 | 0 | uint64_t idle_time = uv_metrics_idle_time(env->event_loop()); |
222 | 0 | writer.json_keyvalue("loopIdleTimeSeconds", 1.0 * idle_time / 1e9); |
223 | 0 | writer.json_end(); |
224 | 0 | } |
225 | |
|
226 | 0 | writer.json_arrayend(); |
227 | |
|
228 | 0 | writer.json_arraystart("workers"); |
229 | 0 | if (env != nullptr) { |
230 | 0 | Mutex workers_mutex; |
231 | 0 | ConditionVariable notify; |
232 | 0 | std::vector<std::string> worker_infos; |
233 | 0 | size_t expected_results = 0; |
234 | |
|
235 | 0 | env->ForEachWorker([&](Worker* w) { |
236 | 0 | expected_results += w->RequestInterrupt([&, w = w](Environment* env) { |
237 | 0 | std::ostringstream os; |
238 | 0 | std::string name = |
239 | 0 | "Worker thread subreport [" + std::string(w->name()) + "]"; |
240 | 0 | GetNodeReport(env, name, trigger, Local<Value>(), os); |
241 | |
|
242 | 0 | Mutex::ScopedLock lock(workers_mutex); |
243 | 0 | worker_infos.emplace_back(os.str()); |
244 | 0 | notify.Signal(lock); |
245 | 0 | }); |
246 | 0 | }); |
247 | |
|
248 | 0 | Mutex::ScopedLock lock(workers_mutex); |
249 | 0 | worker_infos.reserve(expected_results); |
250 | 0 | while (worker_infos.size() < expected_results) |
251 | 0 | notify.Wait(lock); |
252 | 0 | for (const std::string& worker_info : worker_infos) |
253 | 0 | writer.json_element(JSONWriter::ForeignJSON { worker_info }); |
254 | 0 | } |
255 | 0 | writer.json_arrayend(); |
256 | | |
257 | | // Report operating system information |
258 | 0 | if (exclude_env == false) { |
259 | 0 | PrintEnvironmentVariables(&writer); |
260 | 0 | } |
261 | 0 | PrintSystemInformation(&writer); |
262 | |
|
263 | 0 | writer.json_objectend(); |
264 | | |
265 | | // Restore output stream formatting. |
266 | 0 | out.copyfmt(old_state); |
267 | 0 | } |
268 | | |
269 | | // Report Node.js version, OS version and machine information. |
270 | 0 | static void PrintVersionInformation(JSONWriter* writer, bool exclude_network) { |
271 | 0 | std::ostringstream buf; |
272 | | // Report Node version |
273 | 0 | buf << "v" << NODE_VERSION_STRING; |
274 | 0 | writer->json_keyvalue("nodejsVersion", buf.str()); |
275 | 0 | buf.str(""); |
276 | |
|
277 | 0 | #ifndef _WIN32 |
278 | | // Report compiler and runtime glibc versions where possible. |
279 | 0 | const char* (*libc_version)(); |
280 | 0 | *(reinterpret_cast<void**>(&libc_version)) = |
281 | 0 | dlsym(RTLD_DEFAULT, "gnu_get_libc_version"); |
282 | 0 | if (libc_version != nullptr) |
283 | 0 | writer->json_keyvalue("glibcVersionRuntime", (*libc_version)()); |
284 | 0 | #endif /* _WIN32 */ |
285 | |
|
286 | 0 | #ifdef __GLIBC__ |
287 | 0 | buf << __GLIBC__ << "." << __GLIBC_MINOR__; |
288 | 0 | writer->json_keyvalue("glibcVersionCompiler", buf.str()); |
289 | 0 | buf.str(""); |
290 | 0 | #endif |
291 | | |
292 | | // Report Process word size |
293 | 0 | writer->json_keyvalue("wordSize", sizeof(void*) * 8); |
294 | 0 | writer->json_keyvalue("arch", per_process::metadata.arch); |
295 | 0 | writer->json_keyvalue("platform", per_process::metadata.platform); |
296 | | |
297 | | // Report deps component versions |
298 | 0 | PrintComponentVersions(writer); |
299 | | |
300 | | // Report release metadata. |
301 | 0 | PrintRelease(writer); |
302 | | |
303 | | // Report operating system and machine information |
304 | 0 | uv_utsname_t os_info; |
305 | |
|
306 | 0 | if (uv_os_uname(&os_info) == 0) { |
307 | 0 | writer->json_keyvalue("osName", os_info.sysname); |
308 | 0 | writer->json_keyvalue("osRelease", os_info.release); |
309 | 0 | writer->json_keyvalue("osVersion", os_info.version); |
310 | 0 | writer->json_keyvalue("osMachine", os_info.machine); |
311 | 0 | } |
312 | |
|
313 | 0 | PrintCpuInfo(writer); |
314 | 0 | if (!exclude_network) PrintNetworkInterfaceInfo(writer); |
315 | |
|
316 | 0 | char host[UV_MAXHOSTNAMESIZE]; |
317 | 0 | size_t host_size = sizeof(host); |
318 | |
|
319 | 0 | if (uv_os_gethostname(host, &host_size) == 0) |
320 | 0 | writer->json_keyvalue("host", host); |
321 | 0 | } |
322 | | |
323 | | // Report CPU info |
324 | 0 | static void PrintCpuInfo(JSONWriter* writer) { |
325 | 0 | uv_cpu_info_t* cpu_info; |
326 | 0 | int count; |
327 | 0 | if (uv_cpu_info(&cpu_info, &count) == 0) { |
328 | 0 | writer->json_arraystart("cpus"); |
329 | 0 | for (int i = 0; i < count; i++) { |
330 | 0 | writer->json_start(); |
331 | 0 | writer->json_keyvalue("model", cpu_info[i].model); |
332 | 0 | writer->json_keyvalue("speed", cpu_info[i].speed); |
333 | 0 | writer->json_keyvalue("user", cpu_info[i].cpu_times.user); |
334 | 0 | writer->json_keyvalue("nice", cpu_info[i].cpu_times.nice); |
335 | 0 | writer->json_keyvalue("sys", cpu_info[i].cpu_times.sys); |
336 | 0 | writer->json_keyvalue("idle", cpu_info[i].cpu_times.idle); |
337 | 0 | writer->json_keyvalue("irq", cpu_info[i].cpu_times.irq); |
338 | 0 | writer->json_end(); |
339 | 0 | } |
340 | 0 | writer->json_arrayend(); |
341 | 0 | uv_free_cpu_info(cpu_info, count); |
342 | 0 | } |
343 | 0 | } |
344 | | |
345 | 0 | static void PrintNetworkInterfaceInfo(JSONWriter* writer) { |
346 | 0 | uv_interface_address_t* interfaces; |
347 | 0 | char ip[INET6_ADDRSTRLEN]; |
348 | 0 | char netmask[INET6_ADDRSTRLEN]; |
349 | 0 | char mac[18]; |
350 | 0 | int count; |
351 | |
|
352 | 0 | if (uv_interface_addresses(&interfaces, &count) == 0) { |
353 | 0 | writer->json_arraystart("networkInterfaces"); |
354 | |
|
355 | 0 | for (int i = 0; i < count; i++) { |
356 | 0 | writer->json_start(); |
357 | 0 | writer->json_keyvalue("name", interfaces[i].name); |
358 | 0 | writer->json_keyvalue("internal", !!interfaces[i].is_internal); |
359 | 0 | snprintf(mac, |
360 | 0 | sizeof(mac), |
361 | 0 | "%02x:%02x:%02x:%02x:%02x:%02x", |
362 | 0 | static_cast<unsigned char>(interfaces[i].phys_addr[0]), |
363 | 0 | static_cast<unsigned char>(interfaces[i].phys_addr[1]), |
364 | 0 | static_cast<unsigned char>(interfaces[i].phys_addr[2]), |
365 | 0 | static_cast<unsigned char>(interfaces[i].phys_addr[3]), |
366 | 0 | static_cast<unsigned char>(interfaces[i].phys_addr[4]), |
367 | 0 | static_cast<unsigned char>(interfaces[i].phys_addr[5])); |
368 | 0 | writer->json_keyvalue("mac", mac); |
369 | |
|
370 | 0 | if (interfaces[i].address.address4.sin_family == AF_INET) { |
371 | 0 | uv_ip4_name(&interfaces[i].address.address4, ip, sizeof(ip)); |
372 | 0 | uv_ip4_name(&interfaces[i].netmask.netmask4, netmask, sizeof(netmask)); |
373 | 0 | writer->json_keyvalue("address", ip); |
374 | 0 | writer->json_keyvalue("netmask", netmask); |
375 | 0 | writer->json_keyvalue("family", "IPv4"); |
376 | 0 | } else if (interfaces[i].address.address4.sin_family == AF_INET6) { |
377 | 0 | uv_ip6_name(&interfaces[i].address.address6, ip, sizeof(ip)); |
378 | 0 | uv_ip6_name(&interfaces[i].netmask.netmask6, netmask, sizeof(netmask)); |
379 | 0 | writer->json_keyvalue("address", ip); |
380 | 0 | writer->json_keyvalue("netmask", netmask); |
381 | 0 | writer->json_keyvalue("family", "IPv6"); |
382 | 0 | writer->json_keyvalue("scopeid", |
383 | 0 | interfaces[i].address.address6.sin6_scope_id); |
384 | 0 | } else { |
385 | 0 | writer->json_keyvalue("family", "unknown"); |
386 | 0 | } |
387 | |
|
388 | 0 | writer->json_end(); |
389 | 0 | } |
390 | |
|
391 | 0 | writer->json_arrayend(); |
392 | 0 | uv_free_interface_addresses(interfaces, count); |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | | static void PrintJavaScriptErrorProperties(JSONWriter* writer, |
397 | | Isolate* isolate, |
398 | 0 | Local<Value> error) { |
399 | 0 | writer->json_objectstart("errorProperties"); |
400 | 0 | if (!error.IsEmpty() && error->IsObject()) { |
401 | 0 | TryCatch try_catch(isolate); |
402 | 0 | Local<Object> error_obj = error.As<Object>(); |
403 | 0 | Local<Context> context = Isolate::GetCurrent()->GetCurrentContext(); |
404 | 0 | Local<Array> keys; |
405 | 0 | if (!error_obj->GetOwnPropertyNames(context).ToLocal(&keys)) { |
406 | 0 | return writer->json_objectend(); // the end of 'errorProperties' |
407 | 0 | } |
408 | 0 | uint32_t keys_length = keys->Length(); |
409 | 0 | for (uint32_t i = 0; i < keys_length; i++) { |
410 | 0 | Local<Value> key; |
411 | 0 | if (!keys->Get(context, i).ToLocal(&key) || !key->IsString()) { |
412 | 0 | continue; |
413 | 0 | } |
414 | 0 | Local<Value> value; |
415 | 0 | Local<String> value_string; |
416 | 0 | if (!error_obj->Get(context, key).ToLocal(&value) || |
417 | 0 | !value->ToString(context).ToLocal(&value_string)) { |
418 | 0 | continue; |
419 | 0 | } |
420 | 0 | node::Utf8Value k(isolate, key); |
421 | 0 | if (k == "stack" || k == "message") continue; |
422 | 0 | node::Utf8Value v(isolate, value_string); |
423 | 0 | writer->json_keyvalue(k.ToStringView(), v.ToStringView()); |
424 | 0 | } |
425 | 0 | } |
426 | 0 | writer->json_objectend(); // the end of 'errorProperties' |
427 | 0 | } |
428 | | |
429 | | static Maybe<std::string> ErrorToString(Isolate* isolate, |
430 | | Local<Context> context, |
431 | 0 | Local<Value> error) { |
432 | 0 | if (error.IsEmpty()) { |
433 | 0 | return Nothing<std::string>(); |
434 | 0 | } |
435 | | |
436 | 0 | MaybeLocal<String> maybe_str; |
437 | | // `ToString` is not available to Symbols. |
438 | 0 | if (error->IsSymbol()) { |
439 | 0 | maybe_str = error.As<v8::Symbol>()->ToDetailString(context); |
440 | 0 | } else if (!error->IsObject()) { |
441 | 0 | maybe_str = error->ToString(context); |
442 | 0 | } else if (error->IsObject()) { |
443 | 0 | Local<Value> stack; |
444 | 0 | if (!error.As<Object>() |
445 | 0 | ->Get(context, FIXED_ONE_BYTE_STRING(isolate, "stack")) |
446 | 0 | .ToLocal(&stack)) { |
447 | 0 | return Nothing<std::string>(); |
448 | 0 | } |
449 | 0 | if (stack->IsString()) { |
450 | 0 | maybe_str = stack.As<String>(); |
451 | 0 | } |
452 | 0 | } |
453 | | |
454 | 0 | Local<String> js_str; |
455 | 0 | if (!maybe_str.ToLocal(&js_str)) { |
456 | 0 | return Nothing<std::string>(); |
457 | 0 | } |
458 | 0 | return Just(Utf8Value(isolate, js_str).ToString()); |
459 | 0 | } |
460 | | |
461 | 0 | static void PrintEmptyJavaScriptStack(JSONWriter* writer) { |
462 | 0 | writer->json_keyvalue("message", "No stack."); |
463 | 0 | writer->json_arraystart("stack"); |
464 | 0 | writer->json_element("Unavailable."); |
465 | 0 | writer->json_arrayend(); |
466 | |
|
467 | 0 | writer->json_objectstart("errorProperties"); |
468 | 0 | writer->json_objectend(); |
469 | 0 | } |
470 | | |
471 | | // Do our best to report the JavaScript stack without calling into JavaScript. |
472 | | static void PrintJavaScriptStack(JSONWriter* writer, |
473 | | Isolate* isolate, |
474 | 0 | std::string_view trigger) { |
475 | 0 | HandleScope scope(isolate); |
476 | 0 | Local<v8::StackTrace> stack; |
477 | 0 | if (!GetCurrentStackTrace(isolate, MAX_FRAME_COUNT).ToLocal(&stack)) { |
478 | 0 | PrintEmptyJavaScriptStack(writer); |
479 | 0 | return; |
480 | 0 | } |
481 | | |
482 | 0 | RegisterState state; |
483 | 0 | state.pc = nullptr; |
484 | 0 | state.fp = &state; |
485 | 0 | state.sp = &state; |
486 | | |
487 | | // in-out params |
488 | 0 | SampleInfo info; |
489 | 0 | void* samples[MAX_FRAME_COUNT]; |
490 | 0 | isolate->GetStackSample(state, samples, MAX_FRAME_COUNT, &info); |
491 | |
|
492 | 0 | writer->json_keyvalue("message", trigger); |
493 | 0 | writer->json_arraystart("stack"); |
494 | 0 | for (int i = 0; i < stack->GetFrameCount(); i++) { |
495 | 0 | Local<StackFrame> frame = stack->GetFrame(isolate, i); |
496 | |
|
497 | 0 | Utf8Value function_name(isolate, frame->GetFunctionName()); |
498 | 0 | Utf8Value script_name(isolate, frame->GetScriptName()); |
499 | 0 | const int line_number = frame->GetLineNumber(); |
500 | 0 | const int column = frame->GetColumn(); |
501 | |
|
502 | 0 | std::string stack_line = SPrintF( |
503 | 0 | "at %s (%s:%d:%d)", function_name, script_name, line_number, column); |
504 | 0 | writer->json_element(stack_line); |
505 | 0 | } |
506 | 0 | writer->json_arrayend(); |
507 | 0 | writer->json_objectstart("errorProperties"); |
508 | 0 | writer->json_objectend(); |
509 | 0 | } |
510 | | |
511 | | // Report the JavaScript stack. |
512 | | static void PrintJavaScriptErrorStack(JSONWriter* writer, |
513 | | Isolate* isolate, |
514 | | Local<Value> error, |
515 | 0 | std::string_view trigger) { |
516 | 0 | if (error.IsEmpty()) { |
517 | 0 | return PrintJavaScriptStack(writer, isolate, trigger); |
518 | 0 | } |
519 | | |
520 | 0 | TryCatch try_catch(isolate); |
521 | 0 | HandleScope scope(isolate); |
522 | 0 | Local<Context> context = isolate->GetCurrentContext(); |
523 | 0 | std::string ss = ""; |
524 | 0 | if (!ErrorToString(isolate, context, error).To(&ss)) { |
525 | 0 | PrintEmptyJavaScriptStack(writer); |
526 | 0 | return; |
527 | 0 | } |
528 | | |
529 | 0 | int line = ss.find('\n'); |
530 | 0 | if (line == -1) { |
531 | 0 | writer->json_keyvalue("message", ss); |
532 | 0 | } else { |
533 | 0 | std::string l = ss.substr(0, line); |
534 | 0 | writer->json_keyvalue("message", l); |
535 | 0 | writer->json_arraystart("stack"); |
536 | 0 | ss = ss.substr(line + 1); |
537 | 0 | line = ss.find('\n'); |
538 | 0 | while (line != -1) { |
539 | 0 | l = ss.substr(0, line); |
540 | 0 | l.erase(l.begin(), std::ranges::find_if(l, [](int ch) { |
541 | 0 | return !std::iswspace(ch); |
542 | 0 | })); |
543 | 0 | writer->json_element(l); |
544 | 0 | ss = ss.substr(line + 1); |
545 | 0 | line = ss.find('\n'); |
546 | 0 | } |
547 | 0 | writer->json_arrayend(); |
548 | 0 | } |
549 | | |
550 | | // Report summary JavaScript error properties backtrace |
551 | 0 | PrintJavaScriptErrorProperties(writer, isolate, error); |
552 | 0 | } |
553 | | |
554 | | // Report a native stack backtrace |
555 | 0 | static void PrintNativeStack(JSONWriter* writer) { |
556 | 0 | auto sym_ctx = NativeSymbolDebuggingContext::New(); |
557 | 0 | void* frames[256]; |
558 | 0 | const int size = sym_ctx->GetStackTrace(frames, arraysize(frames)); |
559 | 0 | writer->json_arraystart("nativeStack"); |
560 | 0 | int i; |
561 | 0 | for (i = 1; i < size; i++) { |
562 | 0 | void* frame = frames[i]; |
563 | 0 | writer->json_start(); |
564 | 0 | writer->json_keyvalue("pc", |
565 | 0 | ValueToHexString(reinterpret_cast<uintptr_t>(frame))); |
566 | 0 | writer->json_keyvalue("symbol", sym_ctx->LookupSymbol(frame).Display()); |
567 | 0 | writer->json_end(); |
568 | 0 | } |
569 | 0 | writer->json_arrayend(); |
570 | 0 | } |
571 | | |
572 | | // Report V8 JavaScript heap information. |
573 | | // This uses the existing V8 HeapStatistics and HeapSpaceStatistics APIs. |
574 | | // The isolate->GetGCStatistics(&heap_stats) internal V8 API could potentially |
575 | | // provide some more useful information - the GC history and the handle counts |
576 | 0 | static void PrintGCStatistics(JSONWriter* writer, Isolate* isolate) { |
577 | 0 | HeapStatistics v8_heap_stats; |
578 | 0 | isolate->GetHeapStatistics(&v8_heap_stats); |
579 | 0 | HeapSpaceStatistics v8_heap_space_stats; |
580 | |
|
581 | 0 | writer->json_objectstart("javascriptHeap"); |
582 | 0 | writer->json_keyvalue("totalMemory", v8_heap_stats.total_heap_size()); |
583 | 0 | writer->json_keyvalue("executableMemory", |
584 | 0 | v8_heap_stats.total_heap_size_executable()); |
585 | 0 | writer->json_keyvalue("totalCommittedMemory", |
586 | 0 | v8_heap_stats.total_physical_size()); |
587 | 0 | writer->json_keyvalue("availableMemory", |
588 | 0 | v8_heap_stats.total_available_size()); |
589 | 0 | writer->json_keyvalue("totalGlobalHandlesMemory", |
590 | 0 | v8_heap_stats.total_global_handles_size()); |
591 | 0 | writer->json_keyvalue("usedGlobalHandlesMemory", |
592 | 0 | v8_heap_stats.used_global_handles_size()); |
593 | 0 | writer->json_keyvalue("usedMemory", v8_heap_stats.used_heap_size()); |
594 | 0 | writer->json_keyvalue("memoryLimit", v8_heap_stats.heap_size_limit()); |
595 | 0 | writer->json_keyvalue("mallocedMemory", v8_heap_stats.malloced_memory()); |
596 | 0 | writer->json_keyvalue("externalMemory", v8_heap_stats.external_memory()); |
597 | 0 | writer->json_keyvalue("peakMallocedMemory", |
598 | 0 | v8_heap_stats.peak_malloced_memory()); |
599 | 0 | writer->json_keyvalue("nativeContextCount", |
600 | 0 | v8_heap_stats.number_of_native_contexts()); |
601 | 0 | writer->json_keyvalue("detachedContextCount", |
602 | 0 | v8_heap_stats.number_of_detached_contexts()); |
603 | 0 | writer->json_keyvalue("doesZapGarbage", v8_heap_stats.does_zap_garbage()); |
604 | |
|
605 | 0 | writer->json_objectstart("heapSpaces"); |
606 | | // Loop through heap spaces |
607 | 0 | for (size_t i = 0; i < isolate->NumberOfHeapSpaces(); i++) { |
608 | 0 | isolate->GetHeapSpaceStatistics(&v8_heap_space_stats, i); |
609 | 0 | writer->json_objectstart(v8_heap_space_stats.space_name()); |
610 | 0 | writer->json_keyvalue("memorySize", v8_heap_space_stats.space_size()); |
611 | 0 | writer->json_keyvalue( |
612 | 0 | "committedMemory", |
613 | 0 | v8_heap_space_stats.physical_space_size()); |
614 | 0 | writer->json_keyvalue( |
615 | 0 | "capacity", |
616 | 0 | v8_heap_space_stats.space_used_size() + |
617 | 0 | v8_heap_space_stats.space_available_size()); |
618 | 0 | writer->json_keyvalue("used", v8_heap_space_stats.space_used_size()); |
619 | 0 | writer->json_keyvalue( |
620 | 0 | "available", v8_heap_space_stats.space_available_size()); |
621 | 0 | writer->json_objectend(); |
622 | 0 | } |
623 | |
|
624 | 0 | writer->json_objectend(); |
625 | 0 | writer->json_objectend(); |
626 | 0 | } |
627 | | |
628 | 0 | static void PrintResourceUsage(JSONWriter* writer) { |
629 | | // Get process uptime in seconds |
630 | 0 | uint64_t uptime = |
631 | 0 | (uv_hrtime() - per_process::node_start_time) / (NANOS_PER_SEC); |
632 | 0 | if (uptime == 0) uptime = 1; // avoid division by zero. |
633 | | |
634 | | // Process and current thread usage statistics |
635 | 0 | uv_rusage_t rusage; |
636 | 0 | writer->json_objectstart("resourceUsage"); |
637 | |
|
638 | 0 | uint64_t free_memory = uv_get_free_memory(); |
639 | 0 | uint64_t total_memory = uv_get_total_memory(); |
640 | |
|
641 | 0 | writer->json_keyvalue("free_memory", free_memory); |
642 | 0 | writer->json_keyvalue("total_memory", total_memory); |
643 | |
|
644 | 0 | size_t rss; |
645 | 0 | int err = uv_resident_set_memory(&rss); |
646 | 0 | if (!err) { |
647 | 0 | writer->json_keyvalue("rss", rss); |
648 | 0 | } |
649 | |
|
650 | 0 | uint64_t constrained_memory = uv_get_constrained_memory(); |
651 | 0 | if (constrained_memory) { |
652 | 0 | writer->json_keyvalue("constrained_memory", constrained_memory); |
653 | 0 | } |
654 | |
|
655 | 0 | uint64_t available_memory = uv_get_available_memory(); |
656 | 0 | writer->json_keyvalue("available_memory", available_memory); |
657 | |
|
658 | 0 | if (uv_getrusage(&rusage) == 0) { |
659 | 0 | double user_cpu = |
660 | 0 | rusage.ru_utime.tv_sec + SEC_PER_MICROS * rusage.ru_utime.tv_usec; |
661 | 0 | double kernel_cpu = |
662 | 0 | rusage.ru_stime.tv_sec + SEC_PER_MICROS * rusage.ru_stime.tv_usec; |
663 | 0 | writer->json_keyvalue("userCpuSeconds", user_cpu); |
664 | 0 | writer->json_keyvalue("kernelCpuSeconds", kernel_cpu); |
665 | 0 | double cpu_abs = user_cpu + kernel_cpu; |
666 | 0 | double cpu_percentage = (cpu_abs / uptime) * 100.0; |
667 | 0 | double user_cpu_percentage = (user_cpu / uptime) * 100.0; |
668 | 0 | double kernel_cpu_percentage = (kernel_cpu / uptime) * 100.0; |
669 | 0 | writer->json_keyvalue("cpuConsumptionPercent", cpu_percentage); |
670 | 0 | writer->json_keyvalue("userCpuConsumptionPercent", user_cpu_percentage); |
671 | 0 | writer->json_keyvalue("kernelCpuConsumptionPercent", kernel_cpu_percentage); |
672 | 0 | writer->json_keyvalue("maxRss", rusage.ru_maxrss * 1024); |
673 | 0 | writer->json_objectstart("pageFaults"); |
674 | 0 | writer->json_keyvalue("IORequired", rusage.ru_majflt); |
675 | 0 | writer->json_keyvalue("IONotRequired", rusage.ru_minflt); |
676 | 0 | writer->json_objectend(); |
677 | 0 | writer->json_objectstart("fsActivity"); |
678 | 0 | writer->json_keyvalue("reads", rusage.ru_inblock); |
679 | 0 | writer->json_keyvalue("writes", rusage.ru_oublock); |
680 | 0 | writer->json_objectend(); |
681 | 0 | } |
682 | 0 | writer->json_objectend(); |
683 | |
|
684 | 0 | uv_rusage_t stats; |
685 | 0 | if (uv_getrusage_thread(&stats) == 0) { |
686 | 0 | writer->json_objectstart("uvthreadResourceUsage"); |
687 | 0 | double user_cpu = |
688 | 0 | stats.ru_utime.tv_sec + SEC_PER_MICROS * stats.ru_utime.tv_usec; |
689 | 0 | double kernel_cpu = |
690 | 0 | stats.ru_stime.tv_sec + SEC_PER_MICROS * stats.ru_stime.tv_usec; |
691 | 0 | writer->json_keyvalue("userCpuSeconds", user_cpu); |
692 | 0 | writer->json_keyvalue("kernelCpuSeconds", kernel_cpu); |
693 | 0 | double cpu_abs = user_cpu + kernel_cpu; |
694 | 0 | double cpu_percentage = (cpu_abs / uptime) * 100.0; |
695 | 0 | double user_cpu_percentage = (user_cpu / uptime) * 100.0; |
696 | 0 | double kernel_cpu_percentage = (kernel_cpu / uptime) * 100.0; |
697 | 0 | writer->json_keyvalue("cpuConsumptionPercent", cpu_percentage); |
698 | 0 | writer->json_keyvalue("userCpuConsumptionPercent", user_cpu_percentage); |
699 | 0 | writer->json_keyvalue("kernelCpuConsumptionPercent", kernel_cpu_percentage); |
700 | 0 | writer->json_objectstart("fsActivity"); |
701 | 0 | writer->json_keyvalue("reads", stats.ru_inblock); |
702 | 0 | writer->json_keyvalue("writes", stats.ru_oublock); |
703 | 0 | writer->json_objectend(); |
704 | 0 | writer->json_objectend(); |
705 | 0 | } |
706 | 0 | } |
707 | | |
708 | 0 | static void PrintEnvironmentVariables(JSONWriter* writer) { |
709 | 0 | uv_env_item_t* envitems; |
710 | 0 | int envcount; |
711 | 0 | int r; |
712 | |
|
713 | 0 | writer->json_objectstart("environmentVariables"); |
714 | |
|
715 | 0 | { |
716 | 0 | Mutex::ScopedLock lock(per_process::env_var_mutex); |
717 | 0 | r = uv_os_environ(&envitems, &envcount); |
718 | 0 | } |
719 | |
|
720 | 0 | if (r == 0) { |
721 | 0 | for (int i = 0; i < envcount; i++) |
722 | 0 | writer->json_keyvalue(envitems[i].name, envitems[i].value); |
723 | |
|
724 | 0 | uv_os_free_environ(envitems, envcount); |
725 | 0 | } |
726 | |
|
727 | 0 | writer->json_objectend(); |
728 | 0 | } |
729 | | |
730 | | // Report operating system information. |
731 | 0 | static void PrintSystemInformation(JSONWriter* writer) { |
732 | 0 | #ifndef _WIN32 |
733 | 0 | static struct { |
734 | 0 | const char* description; |
735 | 0 | int id; |
736 | 0 | } rlimit_strings[] = { |
737 | 0 | {"core_file_size_blocks", RLIMIT_CORE}, |
738 | 0 | {"data_seg_size_bytes", RLIMIT_DATA}, |
739 | 0 | {"file_size_blocks", RLIMIT_FSIZE}, |
740 | 0 | #if !(defined(_AIX) || defined(__sun)) |
741 | 0 | {"max_locked_memory_bytes", RLIMIT_MEMLOCK}, |
742 | 0 | #endif |
743 | 0 | #ifndef __sun |
744 | 0 | {"max_memory_size_bytes", RLIMIT_RSS}, |
745 | 0 | #endif |
746 | 0 | {"open_files", RLIMIT_NOFILE}, |
747 | 0 | {"stack_size_bytes", RLIMIT_STACK}, |
748 | 0 | {"cpu_time_seconds", RLIMIT_CPU}, |
749 | 0 | #ifndef __sun |
750 | 0 | {"max_user_processes", RLIMIT_NPROC}, |
751 | 0 | #endif |
752 | 0 | #ifndef __OpenBSD__ |
753 | 0 | {"virtual_memory_bytes", RLIMIT_AS} |
754 | 0 | #endif |
755 | 0 | }; |
756 | |
|
757 | 0 | writer->json_objectstart("userLimits"); |
758 | 0 | struct rlimit limit; |
759 | |
|
760 | 0 | for (size_t i = 0; i < arraysize(rlimit_strings); i++) { |
761 | 0 | if (getrlimit(rlimit_strings[i].id, &limit) == 0) { |
762 | 0 | writer->json_objectstart(rlimit_strings[i].description); |
763 | |
|
764 | 0 | if (limit.rlim_cur == RLIM_INFINITY) |
765 | 0 | writer->json_keyvalue("soft", "unlimited"); |
766 | 0 | else |
767 | 0 | writer->json_keyvalue("soft", limit.rlim_cur); |
768 | |
|
769 | 0 | if (limit.rlim_max == RLIM_INFINITY) |
770 | 0 | writer->json_keyvalue("hard", "unlimited"); |
771 | 0 | else |
772 | 0 | writer->json_keyvalue("hard", limit.rlim_max); |
773 | |
|
774 | 0 | writer->json_objectend(); |
775 | 0 | } |
776 | 0 | } |
777 | 0 | writer->json_objectend(); |
778 | 0 | #endif // _WIN32 |
779 | |
|
780 | 0 | PrintLoadedLibraries(writer); |
781 | 0 | } |
782 | | |
783 | | // Report a list of loaded native libraries. |
784 | 0 | static void PrintLoadedLibraries(JSONWriter* writer) { |
785 | 0 | writer->json_arraystart("sharedObjects"); |
786 | 0 | std::vector<std::string> modules = |
787 | 0 | NativeSymbolDebuggingContext::GetLoadedLibraries(); |
788 | 0 | for (auto const& module_name : modules) writer->json_element(module_name); |
789 | 0 | writer->json_arrayend(); |
790 | 0 | } |
791 | | |
792 | | // Obtain and report the node and subcomponent version strings. |
793 | 0 | static void PrintComponentVersions(JSONWriter* writer) { |
794 | 0 | writer->json_objectstart("componentVersions"); |
795 | |
|
796 | 0 | for (const auto& version : per_process::metadata.versions.pairs()) { |
797 | 0 | writer->json_keyvalue(version.first, version.second); |
798 | 0 | } |
799 | |
|
800 | 0 | writer->json_objectend(); |
801 | 0 | } |
802 | | |
803 | | // Report runtime release information. |
804 | 0 | static void PrintRelease(JSONWriter* writer) { |
805 | 0 | writer->json_objectstart("release"); |
806 | 0 | writer->json_keyvalue("name", per_process::metadata.release.name); |
807 | | #if NODE_VERSION_IS_LTS |
808 | | writer->json_keyvalue("lts", per_process::metadata.release.lts); |
809 | | #endif |
810 | |
|
811 | | #ifdef NODE_HAS_RELEASE_URLS |
812 | | writer->json_keyvalue("headersUrl", |
813 | | per_process::metadata.release.headers_url); |
814 | | writer->json_keyvalue("sourceUrl", per_process::metadata.release.source_url); |
815 | | #ifdef _WIN32 |
816 | | writer->json_keyvalue("libUrl", per_process::metadata.release.lib_url); |
817 | | #endif // _WIN32 |
818 | | #endif // NODE_HAS_RELEASE_URLS |
819 | |
|
820 | 0 | writer->json_objectend(); |
821 | 0 | } |
822 | | |
823 | | } // namespace report |
824 | | |
825 | | std::string TriggerNodeReport(Isolate* isolate, |
826 | | Environment* env, |
827 | | std::string_view message, |
828 | | std::string_view trigger, |
829 | | std::string_view name, |
830 | 0 | Local<Value> error) { |
831 | 0 | std::string filename; |
832 | | |
833 | | // Determine the required report filename. In order of priority: |
834 | | // 1) supplied on API 2) configured on startup 3) default generated |
835 | 0 | if (!name.empty()) { |
836 | 0 | filename = name; |
837 | | // we may not always be in a great state when generating a node report |
838 | | // allow for the case where we don't have an env |
839 | 0 | if (env != nullptr) { |
840 | 0 | THROW_IF_INSUFFICIENT_PERMISSIONS( |
841 | 0 | env, permission::PermissionScope::kFileSystemWrite, name, filename); |
842 | | // Filename was specified as API parameter. |
843 | 0 | } |
844 | 0 | } else { |
845 | 0 | std::string report_filename; |
846 | 0 | { |
847 | 0 | Mutex::ScopedLock lock(per_process::cli_options_mutex); |
848 | 0 | report_filename = per_process::cli_options->report_filename; |
849 | 0 | } |
850 | 0 | if (report_filename.length() > 0) { |
851 | | // File name was supplied via start-up option. |
852 | 0 | filename = report_filename; |
853 | 0 | } else { |
854 | 0 | filename = *DiagnosticFilename( |
855 | 0 | env != nullptr ? env->thread_id() : 0, "report", "json"); |
856 | 0 | } |
857 | 0 | if (env != nullptr) { |
858 | 0 | THROW_IF_INSUFFICIENT_PERMISSIONS( |
859 | 0 | env, |
860 | 0 | permission::PermissionScope::kFileSystemWrite, |
861 | 0 | Environment::GetCwd(env->exec_path()), |
862 | 0 | filename); |
863 | 0 | } |
864 | 0 | } |
865 | | |
866 | | // Open the report file stream for writing. Supports stdout/err, |
867 | | // user-specified or (default) generated name |
868 | 0 | std::ofstream outfile; |
869 | 0 | std::ostream* outstream; |
870 | 0 | if (filename == "stdout") { |
871 | 0 | outstream = &std::cout; |
872 | 0 | } else if (filename == "stderr") { |
873 | 0 | outstream = &std::cerr; |
874 | 0 | } else { |
875 | 0 | std::string report_directory; |
876 | 0 | { |
877 | 0 | Mutex::ScopedLock lock(per_process::cli_options_mutex); |
878 | 0 | report_directory = per_process::cli_options->report_directory; |
879 | 0 | } |
880 | | // Regular file. Append filename to directory path if one was specified |
881 | 0 | if (report_directory.length() > 0) { |
882 | 0 | std::string pathname = report_directory + kPathSeparator + filename; |
883 | 0 | outfile.open(pathname, std::ios::out | std::ios::binary); |
884 | 0 | } else { |
885 | 0 | outfile.open(filename, std::ios::out | std::ios::binary); |
886 | 0 | } |
887 | | // Check for errors on the file open |
888 | 0 | if (!outfile.is_open()) { |
889 | 0 | std::cerr << "\nFailed to open Node.js report file: " << filename; |
890 | |
|
891 | 0 | if (report_directory.length() > 0) |
892 | 0 | std::cerr << " directory: " << report_directory; |
893 | |
|
894 | 0 | std::cerr << " (errno: " << errno << ")" << std::endl; |
895 | 0 | return ""; |
896 | 0 | } |
897 | 0 | outstream = &outfile; |
898 | 0 | std::cerr << "\nWriting Node.js report to file: " << filename; |
899 | 0 | } |
900 | | |
901 | 0 | bool compact; |
902 | 0 | { |
903 | 0 | Mutex::ScopedLock lock(per_process::cli_options_mutex); |
904 | 0 | compact = per_process::cli_options->report_compact; |
905 | 0 | } |
906 | |
|
907 | 0 | bool exclude_network = env != nullptr ? env->options()->report_exclude_network |
908 | 0 | : per_process::cli_options->per_isolate |
909 | 0 | ->per_env->report_exclude_network; |
910 | 0 | bool exclude_env = |
911 | 0 | env != nullptr |
912 | 0 | ? env->report_exclude_env() |
913 | 0 | : per_process::cli_options->per_isolate->per_env->report_exclude_env; |
914 | |
|
915 | 0 | report::WriteNodeReport(isolate, |
916 | 0 | env, |
917 | 0 | message, |
918 | 0 | trigger, |
919 | 0 | filename, |
920 | 0 | *outstream, |
921 | 0 | error, |
922 | 0 | compact, |
923 | 0 | exclude_network, |
924 | 0 | exclude_env); |
925 | | |
926 | | // Do not close stdout/stderr, only close files we opened. |
927 | 0 | if (outfile.is_open()) { |
928 | 0 | outfile.close(); |
929 | 0 | } |
930 | | |
931 | | // Do not mix JSON and free-form text on stderr. |
932 | 0 | if (filename != "stderr") { |
933 | 0 | std::cerr << "\nNode.js report completed" << std::endl; |
934 | 0 | } |
935 | 0 | return filename; |
936 | 0 | } |
937 | | |
938 | | // External function to trigger a report, writing to file. |
939 | | std::string TriggerNodeReport(Isolate* isolate, |
940 | | std::string_view message, |
941 | | std::string_view trigger, |
942 | | std::string_view name, |
943 | 0 | Local<Value> error) { |
944 | 0 | Environment* env = nullptr; |
945 | 0 | if (isolate != nullptr) { |
946 | 0 | env = Environment::GetCurrent(isolate); |
947 | 0 | } |
948 | 0 | return TriggerNodeReport(isolate, env, message, trigger, name, error); |
949 | 0 | } |
950 | | |
951 | | // External function to trigger a report, writing to file. |
952 | | std::string TriggerNodeReport(Environment* env, |
953 | | std::string_view message, |
954 | | std::string_view trigger, |
955 | | std::string_view name, |
956 | 0 | Local<Value> error) { |
957 | 0 | return TriggerNodeReport(env != nullptr ? env->isolate() : nullptr, |
958 | 0 | env, |
959 | 0 | message, |
960 | 0 | trigger, |
961 | 0 | name, |
962 | 0 | error); |
963 | 0 | } |
964 | | |
965 | | // External function to trigger a report, writing to a supplied stream. |
966 | | void GetNodeReport(Isolate* isolate, |
967 | | std::string_view message, |
968 | | std::string_view trigger, |
969 | | Local<Value> error, |
970 | 0 | std::ostream& out) { |
971 | 0 | Environment* env = nullptr; |
972 | 0 | if (isolate != nullptr) { |
973 | 0 | env = Environment::GetCurrent(isolate); |
974 | 0 | } |
975 | 0 | bool exclude_network = env != nullptr ? env->options()->report_exclude_network |
976 | 0 | : per_process::cli_options->per_isolate |
977 | 0 | ->per_env->report_exclude_network; |
978 | 0 | bool exclude_env = |
979 | 0 | env != nullptr |
980 | 0 | ? env->report_exclude_env() |
981 | 0 | : per_process::cli_options->per_isolate->per_env->report_exclude_env; |
982 | 0 | report::WriteNodeReport(isolate, |
983 | 0 | env, |
984 | 0 | message, |
985 | 0 | trigger, |
986 | 0 | "", |
987 | 0 | out, |
988 | 0 | error, |
989 | 0 | false, |
990 | 0 | exclude_network, |
991 | 0 | exclude_env); |
992 | 0 | } |
993 | | |
994 | | // External function to trigger a report, writing to a supplied stream. |
995 | | void GetNodeReport(Environment* env, |
996 | | std::string_view message, |
997 | | std::string_view trigger, |
998 | | Local<Value> error, |
999 | 0 | std::ostream& out) { |
1000 | 0 | Isolate* isolate = nullptr; |
1001 | 0 | if (env != nullptr) { |
1002 | 0 | isolate = env->isolate(); |
1003 | 0 | } |
1004 | 0 | bool exclude_network = env != nullptr ? env->options()->report_exclude_network |
1005 | 0 | : per_process::cli_options->per_isolate |
1006 | 0 | ->per_env->report_exclude_network; |
1007 | 0 | bool exclude_env = |
1008 | 0 | env != nullptr |
1009 | 0 | ? env->report_exclude_env() |
1010 | 0 | : per_process::cli_options->per_isolate->per_env->report_exclude_env; |
1011 | 0 | report::WriteNodeReport(isolate, |
1012 | 0 | env, |
1013 | 0 | message, |
1014 | 0 | trigger, |
1015 | 0 | "", |
1016 | 0 | out, |
1017 | 0 | error, |
1018 | 0 | false, |
1019 | 0 | exclude_network, |
1020 | 0 | exclude_env); |
1021 | 0 | } |
1022 | | |
1023 | | } // namespace node |