Skip to content
Permalink
Browse files

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>
  • Loading branch information...
gireeshpunathil authored and targos committed Sep 5, 2018
1 parent 219b1b8 commit 549216a13806e236ba9e2df9cd367633a3a350c0
@@ -493,6 +493,11 @@
dest='without_npm',
help='do not install the bundled npm (package manager)')

parser.add_option('--without-report',
action='store_true',
dest='without_report',
help='build without report')

# Dummy option for backwards compatibility
parser.add_option('--with-snapshot',
action='store_true',
@@ -938,6 +943,7 @@ def configure_node(o):
o['variables']['OS'] = 'android'
o['variables']['node_prefix'] = options.prefix
o['variables']['node_install_npm'] = b(not options.without_npm)
o['variables']['node_report'] = b(not options.without_report)
o['default_configuration'] = 'Debug' if options.debug else 'Release'

host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc()
@@ -352,6 +352,10 @@ function startup() {
} = perf.constants;
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE);

if (getOptionValue('--experimental-report')) {
NativeModule.require('internal/process/report').setup();
}

if (isMainThread) {
return startMainThreadExecution;
} else {
@@ -709,6 +709,7 @@ E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error);
E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error);
E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error);
E('ERR_INVALID_ADDRESS_FAMILY', 'Invalid address family: %s', RangeError);
E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error);
E('ERR_INVALID_ARG_TYPE',
(name, expected, actual) => {
assert(typeof name === 'string', "'name' must be a string");
@@ -98,6 +98,31 @@ function createFatalException() {
// call that threw and was never cleared. So clear it now.
clearDefaultTriggerAsyncId();

// If node-report is enabled, call into its handler to see
// whether it is interested in handling the situation.
// Ignore if the error is scoped inside a domain.
// use == in the checks as we want to allow for null and undefined
if (er == null || er.domain == null) {
try {
const report = internalBinding('report');
if (report != null) {
if (require('internal/options').getOptionValue(
'--experimental-report')) {
const config = {};
report.syncConfig(config, false);
if (Array.isArray(config.events) &&
config.events.includes('exception')) {
if (er) {
report.onUnCaughtException(er.stack);
} else {
report.onUnCaughtException(undefined);
}
}
}
}
} catch {} // NOOP, node_report unavailable.
}

if (exceptionHandlerState.captureFn !== null) {
exceptionHandlerState.captureFn(er);
} else if (!process.emit('uncaughtException', er)) {
@@ -0,0 +1,163 @@
'use strict';

const { emitExperimentalWarning } = require('internal/util');
const {
ERR_INVALID_ARG_TYPE,
ERR_SYNTHETIC } = require('internal/errors').codes;

exports.setup = function() {
const REPORTEVENTS = 1;
const REPORTSIGNAL = 2;
const REPORTFILENAME = 3;
const REPORTPATH = 4;
const REPORTVERBOSE = 5;
if (internalBinding('config').hasReport) {
// If report is enabled, extract the binding and
// wrap the APIs with thin layers, with some error checks.
// user options can come in from CLI / ENV / API.
// CLI and ENV is intercepted in C++ and the API call here (JS).
// So sync up with both sides as appropriate - initially from
// C++ to JS and from JS to C++ whenever the API is called.
// Some events are controlled purely from JS (signal | exception)
// and some from C++ (fatalerror) so this sync-up is essential for
// correct behavior and alignment with the supplied tunables.
const nr = internalBinding('report');

// Keep it un-exposed; lest programs play with it
// leaving us with a lot of unwanted sanity checks.
let config = {
events: [],
signal: 'SIGUSR2',
filename: '',
path: '',
verbose: false
};
const report = {
setDiagnosticReportOptions(options) {
emitExperimentalWarning('report');
// Reuse the null and undefined checks. Save
// space when dealing with large number of arguments.
const list = parseOptions(options);

// Flush the stale entries from report, as
// we are refreshing it, items that the users did not
// touch may be hanging around stale otherwise.
config = {};

// The parseOption method returns an array that include
// the indices at which valid params are present.
list.forEach((i) => {
switch (i) {
case REPORTEVENTS:
if (Array.isArray(options.events))
config.events = options.events;
else
throw new ERR_INVALID_ARG_TYPE('events',
'Array',
options.events);
break;
case REPORTSIGNAL:
if (typeof options.signal !== 'string') {
throw new ERR_INVALID_ARG_TYPE('signal',
'String',
options.signal);
}
process.removeListener(config.signal, handleSignal);
if (config.events.includes('signal'))
process.on(options.signal, handleSignal);
config.signal = options.signal;
break;
case REPORTFILENAME:
if (typeof options.filename !== 'string') {
throw new ERR_INVALID_ARG_TYPE('filename',
'String',
options.filename);
}
config.filename = options.filename;
break;
case REPORTPATH:
if (typeof options.path !== 'string')
throw new ERR_INVALID_ARG_TYPE('path', 'String', options.path);
config.path = options.path;
break;
case REPORTVERBOSE:
if (typeof options.verbose !== 'string' &&
typeof options.verbose !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('verbose',
'Booelan | String' +
' (true|false|yes|no)',
options.verbose);
}
config.verbose = options.verbose;
break;
}
});
// Upload this new config to C++ land
nr.syncConfig(config, true);
},


triggerReport(file, err) {
emitExperimentalWarning('report');
if (err == null) {
if (file == null) {
return nr.triggerReport(new ERR_SYNTHETIC(
'JavaScript Callstack').stack);
}
if (typeof file !== 'string')
throw new ERR_INVALID_ARG_TYPE('file', 'String', file);
return nr.triggerReport(file, new ERR_SYNTHETIC(
'JavaScript Callstack').stack);
}
if (typeof err !== 'object')
throw new ERR_INVALID_ARG_TYPE('err', 'Object', err);
if (file == null)
return nr.triggerReport(err.stack);
if (typeof file !== 'string')
throw new ERR_INVALID_ARG_TYPE('file', 'String', file);
return nr.triggerReport(file, err.stack);
},
getReport(err) {
emitExperimentalWarning('report');
if (err == null) {
return nr.getReport(new ERR_SYNTHETIC('JavaScript Callstack').stack);
} else if (typeof err !== 'object') {
throw new ERR_INVALID_ARG_TYPE('err', 'Objct', err);
} else {
return nr.getReport(err.stack);
}
}
};

// Download the CLI / ENV config into JS land.
nr.syncConfig(config, false);

function handleSignal(signo) {
if (typeof signo !== 'string')
signo = config.signal;
nr.onUserSignal(signo);
}

if (config.events.includes('signal')) {
process.on(config.signal, handleSignal);
}

function parseOptions(obj) {
const list = [];
if (obj == null)
return list;
if (obj.events != null)
list.push(REPORTEVENTS);
if (obj.signal != null)
list.push(REPORTSIGNAL);
if (obj.filename != null)
list.push(REPORTFILENAME);
if (obj.path != null)
list.push(REPORTPATH);
if (obj.verbose != null)
list.push(REPORTVERBOSE);
return list;
}
process.report = report;
}
};
@@ -155,6 +155,7 @@
'lib/internal/process/stdio.js',
'lib/internal/process/warning.js',
'lib/internal/process/worker_thread_only.js',
'lib/internal/process/report.js',
'lib/internal/querystring.js',
'lib/internal/queue_microtask.js',
'lib/internal/readline.js',
@@ -313,6 +314,29 @@
# the executable and rename it back to node.exe later
'product_name': '<(node_core_target_name)-win',
}],
[ 'node_report=="true"', {
'defines': [
'NODE_REPORT',
'NODE_ARCH="<(target_arch)"',
'NODE_PLATFORM="<(OS)"',
],
'conditions': [
['OS=="win"', {
'libraries': [
'dbghelp.lib',
'Netapi32.lib',
'PsApi.lib',
'Ws2_32.lib',
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll',
'PsApi.dll',
'Ws2_32.dll',
],
}],
],
}],
],
}, # node_core_target_name
{
@@ -622,6 +646,34 @@
'src/tls_wrap.h'
],
}],
[ 'node_report=="true"', {
'sources': [
'src/node_report.cc',
'src/node_report_module.cc',
'src/node_report_utils.cc',
],
'defines': [
'NODE_REPORT',
'NODE_ARCH="<(target_arch)"',
'NODE_PLATFORM="<(OS)"',
],
'conditions': [
['OS=="win"', {
'libraries': [
'dbghelp.lib',
'Netapi32.lib',
'PsApi.lib',
'Ws2_32.lib',
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll',
'PsApi.dll',
'Ws2_32.dll',
],
}],
],
}],
[ 'node_use_large_pages=="true" and OS=="linux"', {
'defines': [ 'NODE_ENABLE_LARGE_CODE_PAGES=1' ],
# The current implementation of Large Pages is under Linux.
@@ -963,6 +1015,29 @@
'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ],
},
}],
[ 'node_report=="true"', {
'defines': [
'NODE_REPORT',
'NODE_ARCH="<(target_arch)"',
'NODE_PLATFORM="<(OS)"',
],
'conditions': [
['OS=="win"', {
'libraries': [
'dbghelp.lib',
'Netapi32.lib',
'PsApi.lib',
'Ws2_32.lib',
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll',
'PsApi.dll',
'Ws2_32.dll',
],
}],
],
}],
],
}, # cctest
], # end targets
@@ -89,6 +89,10 @@
#include <unicode/uvernum.h>
#endif

#ifdef NODE_REPORT
#include "node_report.h"
#endif

#if defined(LEAK_SANITIZER)
#include <sanitizer/lsan_interface.h>
#endif
@@ -724,6 +728,12 @@ void RunBootstrapping(Environment* env) {
return;
}

#ifdef NODE_REPORT
if (env->options()->experimental_report) {
report::InitializeReport(env->isolate(), env);
}
#endif // NODE_REPORT

// process, loaderExports, isMainThread
std::vector<Local<String>> node_params = {
env->process_string(),
@@ -960,6 +970,12 @@ int Init(std::vector<std::string>* argv,
// Make inherited handles noninheritable.
uv_disable_stdio_inheritance();

#ifdef NODE_REPORT
// Cache the original command line to be
// used in diagnostic reports.
per_process::cli_options->cmdline = *argv;
#endif // NODE_REPORT

#if defined(NODE_V8_OPTIONS)
// Should come before the call to V8::SetFlagsFromCommandLine()
// so the user can disable a flag --foo at run-time by passing
@@ -15,6 +15,12 @@
#define NODE_BUILTIN_ICU_MODULES(V)
#endif

#if NODE_REPORT
#define NODE_BUILTIN_REPORT_MODULES(V) V(report)
#else
#define NODE_BUILTIN_REPORT_MODULES(V)
#endif

// A list of built-in modules. In order to do module registration
// in node::Init(), need to add built-in modules in the following list.
// Then in binding::RegisterBuiltinModules(), it calls modules' registration
@@ -70,7 +76,8 @@
#define NODE_BUILTIN_MODULES(V) \
NODE_BUILTIN_STANDARD_MODULES(V) \
NODE_BUILTIN_OPENSSL_MODULES(V) \
NODE_BUILTIN_ICU_MODULES(V)
NODE_BUILTIN_ICU_MODULES(V) \
NODE_BUILTIN_REPORT_MODULES(V)

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

0 comments on commit 549216a

Please sign in to comment.
You can’t perform that action at this time.