Skip to content

Commit 549216a

Browse files
gireeshpunathiltargos
authored andcommittedJan 24, 2019
src: merge into core
Make node-report part of core runtime because: 1. When enabled, node-report significantly helps root cause various types of problems, including support issues sent to the various repos of the Node.js organization. 2. The requirement of explicitly adding the dependency to node-report in user applications often represents a blocker to adoption. Major deviation from the module version of the node-report is that the report is generated in JSON format, as opposed to human readable text. No new functionalities have been added, changes that are required for melding it as a built-in capability has been affected on the module version of node-report (https://github.com/nodejs/node-report) Co-authored-by: Bidisha Pyne <bidipyne@in.ibm.com> Co-authored-by: Howard Hellyer <hhellyer@uk.ibm.com> Co-authored-by: Jeremiah Senkpiel <fishrock123@rocketmail.com> Co-authored-by: Julian Alimin <dmastag@yahoo.com> Co-authored-by: Lakshmi Swetha Gopireddy <lakshmigopireddy@in.ibm.com> Co-authored-by: Manusaporn Treerungroj <m.treerungroj@gmail.com> Co-authored-by: Michael Dawson <michael_dawson@ca.ibm.com> Co-authored-by: Richard Chamberlain <richard_chamberlain@uk.ibm.com> Co-authored-by: Richard Lau <riclau@uk.ibm.com> Co-authored-by: Sam Roberts <vieuxtech@gmail.com> Co-authored-by: Vipin Menon <vipinmv1@in.ibm.com> PR-URL: #22712 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michael Dawson <Michael_Dawson@ca.ibm.com> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
1 parent 219b1b8 commit 549216a

16 files changed

+1995
-2
lines changed
 

‎configure.py

+6
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,11 @@
493493
dest='without_npm',
494494
help='do not install the bundled npm (package manager)')
495495

496+
parser.add_option('--without-report',
497+
action='store_true',
498+
dest='without_report',
499+
help='build without report')
500+
496501
# Dummy option for backwards compatibility
497502
parser.add_option('--with-snapshot',
498503
action='store_true',
@@ -938,6 +943,7 @@ def configure_node(o):
938943
o['variables']['OS'] = 'android'
939944
o['variables']['node_prefix'] = options.prefix
940945
o['variables']['node_install_npm'] = b(not options.without_npm)
946+
o['variables']['node_report'] = b(not options.without_report)
941947
o['default_configuration'] = 'Debug' if options.debug else 'Release'
942948

943949
host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc()

‎lib/internal/bootstrap/node.js

+4
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,10 @@ function startup() {
352352
} = perf.constants;
353353
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE);
354354

355+
if (getOptionValue('--experimental-report')) {
356+
NativeModule.require('internal/process/report').setup();
357+
}
358+
355359
if (isMainThread) {
356360
return startMainThreadExecution;
357361
} else {

‎lib/internal/errors.js

+1
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,7 @@ E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error);
709709
E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error);
710710
E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error);
711711
E('ERR_INVALID_ADDRESS_FAMILY', 'Invalid address family: %s', RangeError);
712+
E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error);
712713
E('ERR_INVALID_ARG_TYPE',
713714
(name, expected, actual) => {
714715
assert(typeof name === 'string', "'name' must be a string");

‎lib/internal/process/execution.js

+25
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,31 @@ function createFatalException() {
9898
// call that threw and was never cleared. So clear it now.
9999
clearDefaultTriggerAsyncId();
100100

101+
// If node-report is enabled, call into its handler to see
102+
// whether it is interested in handling the situation.
103+
// Ignore if the error is scoped inside a domain.
104+
// use == in the checks as we want to allow for null and undefined
105+
if (er == null || er.domain == null) {
106+
try {
107+
const report = internalBinding('report');
108+
if (report != null) {
109+
if (require('internal/options').getOptionValue(
110+
'--experimental-report')) {
111+
const config = {};
112+
report.syncConfig(config, false);
113+
if (Array.isArray(config.events) &&
114+
config.events.includes('exception')) {
115+
if (er) {
116+
report.onUnCaughtException(er.stack);
117+
} else {
118+
report.onUnCaughtException(undefined);
119+
}
120+
}
121+
}
122+
}
123+
} catch {} // NOOP, node_report unavailable.
124+
}
125+
101126
if (exceptionHandlerState.captureFn !== null) {
102127
exceptionHandlerState.captureFn(er);
103128
} else if (!process.emit('uncaughtException', er)) {

‎lib/internal/process/report.js

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
'use strict';
2+
3+
const { emitExperimentalWarning } = require('internal/util');
4+
const {
5+
ERR_INVALID_ARG_TYPE,
6+
ERR_SYNTHETIC } = require('internal/errors').codes;
7+
8+
exports.setup = function() {
9+
const REPORTEVENTS = 1;
10+
const REPORTSIGNAL = 2;
11+
const REPORTFILENAME = 3;
12+
const REPORTPATH = 4;
13+
const REPORTVERBOSE = 5;
14+
if (internalBinding('config').hasReport) {
15+
// If report is enabled, extract the binding and
16+
// wrap the APIs with thin layers, with some error checks.
17+
// user options can come in from CLI / ENV / API.
18+
// CLI and ENV is intercepted in C++ and the API call here (JS).
19+
// So sync up with both sides as appropriate - initially from
20+
// C++ to JS and from JS to C++ whenever the API is called.
21+
// Some events are controlled purely from JS (signal | exception)
22+
// and some from C++ (fatalerror) so this sync-up is essential for
23+
// correct behavior and alignment with the supplied tunables.
24+
const nr = internalBinding('report');
25+
26+
// Keep it un-exposed; lest programs play with it
27+
// leaving us with a lot of unwanted sanity checks.
28+
let config = {
29+
events: [],
30+
signal: 'SIGUSR2',
31+
filename: '',
32+
path: '',
33+
verbose: false
34+
};
35+
const report = {
36+
setDiagnosticReportOptions(options) {
37+
emitExperimentalWarning('report');
38+
// Reuse the null and undefined checks. Save
39+
// space when dealing with large number of arguments.
40+
const list = parseOptions(options);
41+
42+
// Flush the stale entries from report, as
43+
// we are refreshing it, items that the users did not
44+
// touch may be hanging around stale otherwise.
45+
config = {};
46+
47+
// The parseOption method returns an array that include
48+
// the indices at which valid params are present.
49+
list.forEach((i) => {
50+
switch (i) {
51+
case REPORTEVENTS:
52+
if (Array.isArray(options.events))
53+
config.events = options.events;
54+
else
55+
throw new ERR_INVALID_ARG_TYPE('events',
56+
'Array',
57+
options.events);
58+
break;
59+
case REPORTSIGNAL:
60+
if (typeof options.signal !== 'string') {
61+
throw new ERR_INVALID_ARG_TYPE('signal',
62+
'String',
63+
options.signal);
64+
}
65+
process.removeListener(config.signal, handleSignal);
66+
if (config.events.includes('signal'))
67+
process.on(options.signal, handleSignal);
68+
config.signal = options.signal;
69+
break;
70+
case REPORTFILENAME:
71+
if (typeof options.filename !== 'string') {
72+
throw new ERR_INVALID_ARG_TYPE('filename',
73+
'String',
74+
options.filename);
75+
}
76+
config.filename = options.filename;
77+
break;
78+
case REPORTPATH:
79+
if (typeof options.path !== 'string')
80+
throw new ERR_INVALID_ARG_TYPE('path', 'String', options.path);
81+
config.path = options.path;
82+
break;
83+
case REPORTVERBOSE:
84+
if (typeof options.verbose !== 'string' &&
85+
typeof options.verbose !== 'boolean') {
86+
throw new ERR_INVALID_ARG_TYPE('verbose',
87+
'Booelan | String' +
88+
' (true|false|yes|no)',
89+
options.verbose);
90+
}
91+
config.verbose = options.verbose;
92+
break;
93+
}
94+
});
95+
// Upload this new config to C++ land
96+
nr.syncConfig(config, true);
97+
},
98+
99+
100+
triggerReport(file, err) {
101+
emitExperimentalWarning('report');
102+
if (err == null) {
103+
if (file == null) {
104+
return nr.triggerReport(new ERR_SYNTHETIC(
105+
'JavaScript Callstack').stack);
106+
}
107+
if (typeof file !== 'string')
108+
throw new ERR_INVALID_ARG_TYPE('file', 'String', file);
109+
return nr.triggerReport(file, new ERR_SYNTHETIC(
110+
'JavaScript Callstack').stack);
111+
}
112+
if (typeof err !== 'object')
113+
throw new ERR_INVALID_ARG_TYPE('err', 'Object', err);
114+
if (file == null)
115+
return nr.triggerReport(err.stack);
116+
if (typeof file !== 'string')
117+
throw new ERR_INVALID_ARG_TYPE('file', 'String', file);
118+
return nr.triggerReport(file, err.stack);
119+
},
120+
getReport(err) {
121+
emitExperimentalWarning('report');
122+
if (err == null) {
123+
return nr.getReport(new ERR_SYNTHETIC('JavaScript Callstack').stack);
124+
} else if (typeof err !== 'object') {
125+
throw new ERR_INVALID_ARG_TYPE('err', 'Objct', err);
126+
} else {
127+
return nr.getReport(err.stack);
128+
}
129+
}
130+
};
131+
132+
// Download the CLI / ENV config into JS land.
133+
nr.syncConfig(config, false);
134+
135+
function handleSignal(signo) {
136+
if (typeof signo !== 'string')
137+
signo = config.signal;
138+
nr.onUserSignal(signo);
139+
}
140+
141+
if (config.events.includes('signal')) {
142+
process.on(config.signal, handleSignal);
143+
}
144+
145+
function parseOptions(obj) {
146+
const list = [];
147+
if (obj == null)
148+
return list;
149+
if (obj.events != null)
150+
list.push(REPORTEVENTS);
151+
if (obj.signal != null)
152+
list.push(REPORTSIGNAL);
153+
if (obj.filename != null)
154+
list.push(REPORTFILENAME);
155+
if (obj.path != null)
156+
list.push(REPORTPATH);
157+
if (obj.verbose != null)
158+
list.push(REPORTVERBOSE);
159+
return list;
160+
}
161+
process.report = report;
162+
}
163+
};

‎node.gyp

+75
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
'lib/internal/process/stdio.js',
156156
'lib/internal/process/warning.js',
157157
'lib/internal/process/worker_thread_only.js',
158+
'lib/internal/process/report.js',
158159
'lib/internal/querystring.js',
159160
'lib/internal/queue_microtask.js',
160161
'lib/internal/readline.js',
@@ -313,6 +314,29 @@
313314
# the executable and rename it back to node.exe later
314315
'product_name': '<(node_core_target_name)-win',
315316
}],
317+
[ 'node_report=="true"', {
318+
'defines': [
319+
'NODE_REPORT',
320+
'NODE_ARCH="<(target_arch)"',
321+
'NODE_PLATFORM="<(OS)"',
322+
],
323+
'conditions': [
324+
['OS=="win"', {
325+
'libraries': [
326+
'dbghelp.lib',
327+
'Netapi32.lib',
328+
'PsApi.lib',
329+
'Ws2_32.lib',
330+
],
331+
'dll_files': [
332+
'dbghelp.dll',
333+
'Netapi32.dll',
334+
'PsApi.dll',
335+
'Ws2_32.dll',
336+
],
337+
}],
338+
],
339+
}],
316340
],
317341
}, # node_core_target_name
318342
{
@@ -622,6 +646,34 @@
622646
'src/tls_wrap.h'
623647
],
624648
}],
649+
[ 'node_report=="true"', {
650+
'sources': [
651+
'src/node_report.cc',
652+
'src/node_report_module.cc',
653+
'src/node_report_utils.cc',
654+
],
655+
'defines': [
656+
'NODE_REPORT',
657+
'NODE_ARCH="<(target_arch)"',
658+
'NODE_PLATFORM="<(OS)"',
659+
],
660+
'conditions': [
661+
['OS=="win"', {
662+
'libraries': [
663+
'dbghelp.lib',
664+
'Netapi32.lib',
665+
'PsApi.lib',
666+
'Ws2_32.lib',
667+
],
668+
'dll_files': [
669+
'dbghelp.dll',
670+
'Netapi32.dll',
671+
'PsApi.dll',
672+
'Ws2_32.dll',
673+
],
674+
}],
675+
],
676+
}],
625677
[ 'node_use_large_pages=="true" and OS=="linux"', {
626678
'defines': [ 'NODE_ENABLE_LARGE_CODE_PAGES=1' ],
627679
# The current implementation of Large Pages is under Linux.
@@ -963,6 +1015,29 @@
9631015
'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ],
9641016
},
9651017
}],
1018+
[ 'node_report=="true"', {
1019+
'defines': [
1020+
'NODE_REPORT',
1021+
'NODE_ARCH="<(target_arch)"',
1022+
'NODE_PLATFORM="<(OS)"',
1023+
],
1024+
'conditions': [
1025+
['OS=="win"', {
1026+
'libraries': [
1027+
'dbghelp.lib',
1028+
'Netapi32.lib',
1029+
'PsApi.lib',
1030+
'Ws2_32.lib',
1031+
],
1032+
'dll_files': [
1033+
'dbghelp.dll',
1034+
'Netapi32.dll',
1035+
'PsApi.dll',
1036+
'Ws2_32.dll',
1037+
],
1038+
}],
1039+
],
1040+
}],
9661041
],
9671042
}, # cctest
9681043
], # end targets

‎src/node.cc

+16
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@
8989
#include <unicode/uvernum.h>
9090
#endif
9191

92+
#ifdef NODE_REPORT
93+
#include "node_report.h"
94+
#endif
95+
9296
#if defined(LEAK_SANITIZER)
9397
#include <sanitizer/lsan_interface.h>
9498
#endif
@@ -724,6 +728,12 @@ void RunBootstrapping(Environment* env) {
724728
return;
725729
}
726730

731+
#ifdef NODE_REPORT
732+
if (env->options()->experimental_report) {
733+
report::InitializeReport(env->isolate(), env);
734+
}
735+
#endif // NODE_REPORT
736+
727737
// process, loaderExports, isMainThread
728738
std::vector<Local<String>> node_params = {
729739
env->process_string(),
@@ -960,6 +970,12 @@ int Init(std::vector<std::string>* argv,
960970
// Make inherited handles noninheritable.
961971
uv_disable_stdio_inheritance();
962972

973+
#ifdef NODE_REPORT
974+
// Cache the original command line to be
975+
// used in diagnostic reports.
976+
per_process::cli_options->cmdline = *argv;
977+
#endif // NODE_REPORT
978+
963979
#if defined(NODE_V8_OPTIONS)
964980
// Should come before the call to V8::SetFlagsFromCommandLine()
965981
// so the user can disable a flag --foo at run-time by passing

‎src/node_binding.cc

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
#define NODE_BUILTIN_ICU_MODULES(V)
1616
#endif
1717

18+
#if NODE_REPORT
19+
#define NODE_BUILTIN_REPORT_MODULES(V) V(report)
20+
#else
21+
#define NODE_BUILTIN_REPORT_MODULES(V)
22+
#endif
23+
1824
// A list of built-in modules. In order to do module registration
1925
// in node::Init(), need to add built-in modules in the following list.
2026
// Then in binding::RegisterBuiltinModules(), it calls modules' registration
@@ -70,7 +76,8 @@
7076
#define NODE_BUILTIN_MODULES(V) \
7177
NODE_BUILTIN_STANDARD_MODULES(V) \
7278
NODE_BUILTIN_OPENSSL_MODULES(V) \
73-
NODE_BUILTIN_ICU_MODULES(V)
79+
NODE_BUILTIN_ICU_MODULES(V) \
80+
NODE_BUILTIN_REPORT_MODULES(V)
7481

7582
// This is used to load built-in modules. Instead of using
7683
// __attribute__((constructor)), we call the _register_<modname>

0 commit comments

Comments
 (0)