rollbar
Version:
Effortlessly track and debug errors in your JavaScript applications with Rollbar. This package includes advanced error tracking features and an intuitive interface to help you identify and fix issues more quickly.
317 lines (279 loc) • 7.78 kB
JavaScript
import http from 'http';
import https from 'https';
import replace from '../utility/replace.js';
import * as _ from '../utility.js';
import * as urlHelpers from './telemetry/urlHelpers.js';
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',
);
if (typeof globalThis.fetch === 'function') {
replace(
globalThis,
'fetch',
fetchRequestWrapper.bind(this),
this.replacements,
'network',
);
}
};
function networkRequestWrapper(orig) {
var telemeter = this.telemeter;
return (...args) => {
const [url, options, cb] = args;
var mergedOptions = urlHelpers.mergeOptions(url, options, cb);
const requestUrl = urlHelpers.constructUrl(mergedOptions.options);
const sessionId = _.getSessionIdFromAsyncLocalStorage(this.rollbar.client);
if (
sessionId &&
_.shouldAddBaggageHeader(this.options, { sessionId }, requestUrl)
) {
if (!mergedOptions.options.headers) {
mergedOptions.options.headers = {};
}
mergedOptions.options.headers.baggage = `rollbar.session.id=${sessionId}`;
}
var metadata = {
method: mergedOptions.options.method || 'GET',
url: requestUrl,
status_code: null,
start_time_ms: _.now(),
end_time_ms: null,
};
if (this.autoInstrument.networkRequestHeaders) {
metadata.request_headers = mergedOptions.options.headers;
}
telemeter.captureNetwork(metadata, 'http');
var wrappedCallback = responseCallbackWrapper(
this.autoInstrument,
metadata,
mergedOptions.cb,
);
if (mergedOptions.cb) {
args.pop();
}
args.push(wrappedCallback);
var req = orig.apply(https, args);
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);
}
};
}
function fetchRequestWrapper(orig) {
var telemeter = this.telemeter;
return (...args) => {
const input = args[0];
const init = args[1];
let method = 'GET';
let url;
const sessionId = _.getSessionIdFromAsyncLocalStorage(this.rollbar.client);
if (_.isType(input, 'string') || input instanceof URL) {
url = input.toString();
} else if (input) {
url = input.url;
if (input.method) {
method = input.method;
}
}
if (init && init.method) {
method = init.method;
}
if (
sessionId &&
_.shouldAddBaggageHeader(this.options, { sessionId }, url)
) {
const headers = { baggage: `rollbar.session.id=${sessionId}` };
_.addHeadersToFetch(args, headers);
}
const metadata = {
method: method,
url: url,
status_code: null,
start_time_ms: _.now(),
end_time_ms: null,
};
if (this.autoInstrument.networkRequestHeaders) {
const requestHeaders = normalizeFetchHeaders(
init && init.headers ? init.headers : input && input.headers,
);
if (requestHeaders) {
metadata.request_headers = requestHeaders;
}
}
telemeter.captureNetwork(metadata, 'fetch');
return orig.apply(globalThis, args).then(
(res) => {
metadata.end_time_ms = _.now();
metadata.status_code = res.status;
if (this.autoInstrument.networkResponseHeaders) {
const responseHeaders = normalizeFetchHeaders(res.headers);
if (responseHeaders) {
metadata.response = metadata.response || {};
metadata.response.headers = responseHeaders;
}
}
return res;
},
(err) => {
metadata.end_time_ms = _.now();
metadata.status_code = 0;
metadata.error = [err.name, err.message].join(': ');
throw err;
},
);
};
}
function normalizeFetchHeaders(headers) {
if (!headers) return null;
if (typeof headers.forEach === 'function') {
const normalized = {};
headers.forEach((value, key) => {
normalized[key] = value;
});
return normalized;
}
if (Array.isArray(headers)) {
const normalized = {};
headers.forEach((pair) => {
if (pair && pair.length > 1) {
normalized[pair[0]] = pair[1];
}
});
return normalized;
}
if (_.isType(headers, 'object')) {
return headers;
}
return null;
}
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];
}
}
export default Instrumenter;