var http = require('http'); var https = require('https'); var _ = require('../utility'); var replace = require('../utility/replace'); var urlHelpers = require('./telemetry/urlHelpers'); var defaults = { network: true, networkResponseHeaders: false, networkRequestHeaders: false, log: true, }; function Instrumenter(options, telemeter, rollbar) { this.options = options; var autoInstrument = options.autoInstrument; if (options.enabled === false || autoInstrument === false) { this.autoInstrument = {}; } else { if (!_.isType(autoInstrument, 'object')) { autoInstrument = defaults; } this.autoInstrument = _.merge(defaults, autoInstrument); } this.telemeter = telemeter; this.rollbar = rollbar; this.diagnostic = rollbar.client.notifier.diagnostic; this.replacements = { network: [], log: [], }; } Instrumenter.prototype.configure = function (options) { this.options = _.merge(this.options, options); var autoInstrument = options.autoInstrument; var oldSettings = _.merge(this.autoInstrument); if (options.enabled === false || autoInstrument === false) { this.autoInstrument = {}; } else { if (!_.isType(autoInstrument, 'object')) { autoInstrument = defaults; } this.autoInstrument = _.merge(defaults, autoInstrument); } this.instrument(oldSettings); }; Instrumenter.prototype.instrument = function (oldSettings) { if (this.autoInstrument.network && !(oldSettings && oldSettings.network)) { this.instrumentNetwork(); } else if ( !this.autoInstrument.network && oldSettings && oldSettings.network ) { this.deinstrumentNetwork(); } if (this.autoInstrument.log && !(oldSettings && oldSettings.log)) { this.instrumentConsole(); } else if (!this.autoInstrument.log && oldSettings && oldSettings.log) { this.deinstrumentConsole(); } }; Instrumenter.prototype.deinstrumentNetwork = function () { restore(this.replacements, 'network'); }; Instrumenter.prototype.instrumentNetwork = function () { replace( http, 'request', networkRequestWrapper.bind(this), this.replacements, 'network', ); replace( https, 'request', networkRequestWrapper.bind(this), this.replacements, 'network', ); }; function networkRequestWrapper(orig) { var telemeter = this.telemeter; var self = this; return function (url, options, cb) { var mergedOptions = urlHelpers.mergeOptions(url, options, cb); var metadata = { method: mergedOptions.options.method || 'GET', url: urlHelpers.constructUrl(mergedOptions.options), status_code: null, start_time_ms: _.now(), end_time_ms: null, }; if (self.autoInstrument.networkRequestHeaders) { metadata.request_headers = mergedOptions.options.headers; } telemeter.captureNetwork(metadata, 'http'); // Call the original method with the original arguments and wrapped callback. var wrappedArgs = Array.from(arguments); var wrappedCallback = responseCallbackWrapper( self.autoInstrument, metadata, mergedOptions.cb, ); if (mergedOptions.cb) { wrappedArgs.pop(); } wrappedArgs.push(wrappedCallback); var req = orig.apply(https, wrappedArgs); req.on('error', (err) => { metadata.status_code = 0; metadata.error = [err.name, err.message].join(': '); }); return req; }; } function responseCallbackWrapper(options, metadata, callback) { return function (res) { metadata.end_time_ms = _.now(); metadata.status_code = res.statusCode; metadata.response = {}; if (options.networkResponseHeaders) { metadata.response.headers = res.headers; } if (callback) { return callback.apply(undefined, arguments); } }; } Instrumenter.prototype.captureNetwork = function ( metadata, subtype, rollbarUUID, ) { return this.telemeter.captureNetwork(metadata, subtype, rollbarUUID); }; Instrumenter.prototype.deinstrumentConsole = function () { restore(this.replacements, 'log'); }; Instrumenter.prototype.instrumentConsole = function () { var telemeter = this.telemeter; var stdout = process.stdout; replace( stdout, 'write', function (orig) { return function (string) { telemeter.captureLog(string, 'info'); return orig.apply(stdout, arguments); }; }, this.replacements, 'log', ); var stderr = process.stderr; replace( stderr, 'write', function (orig) { return function (string) { telemeter.captureLog(string, 'error'); return orig.apply(stderr, arguments); }; }, this.replacements, 'log', ); }; function restore(replacements, type) { var b; while (replacements[type].length) { b = replacements[type].shift(); b[0][b[1]] = b[2]; } } module.exports = Instrumenter;