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.
760 lines (703 loc) • 20 kB
JavaScript
var util = require('util');
var os = require('os');
var packageJson = require('../../package.json');
var Client = require('../rollbar');
var _ = require('../utility');
var API = require('../api');
var logger = require('./logger');
var Transport = require('./transport');
var urllib = require('url');
var jsonBackup = require('json-stringify-safe');
var Telemeter = require('../telemetry');
var Instrumenter = require('./telemetry');
var transforms = require('./transforms');
var sharedTransforms = require('../transforms');
var sharedPredicates = require('../predicates');
var truncation = require('../truncation');
var polyfillJSON = require('../../vendor/JSON-js/json3');
function Rollbar(options, client) {
if (_.isType(options, 'string')) {
var accessToken = options;
options = {};
options.accessToken = accessToken;
}
if (options.minimumLevel !== undefined) {
options.reportLevel = options.minimumLevel;
delete options.minimumLevel;
}
this.options = _.handleOptions(Rollbar.defaultOptions, options, null, logger);
this.options._configuredOptions = options;
// On the server we want to ignore any maxItems setting
delete this.options.maxItems;
this.options.environment = this.options.environment || 'unspecified';
logger.setVerbose(this.options.verbose);
this.lambdaContext = null;
this.lambdaTimeoutHandle = null;
var transport = new Transport();
var api = new API(this.options, transport, urllib, truncation, jsonBackup);
var telemeter = new Telemeter(this.options);
this.client =
client || new Client(this.options, api, logger, telemeter, 'server');
this.instrumenter = new Instrumenter(
this.options,
this.client.telemeter,
this,
);
this.instrumenter.instrument();
if (this.options.locals) {
this.locals = initLocals(this.options.locals, logger);
}
addTransformsToNotifier(this.client.notifier);
addPredicatesToQueue(this.client.queue);
this.setupUnhandledCapture();
_.setupJSON(polyfillJSON);
}
function initLocals(localsOptions, logger) {
// Capturing stack local variables is only supported in Node 10 and higher.
var nodeMajorVersion = process.versions.node.split('.')[0];
if (nodeMajorVersion < 10) {
return null;
}
var Locals;
if (typeof localsOptions === 'function') {
Locals = localsOptions;
localsOptions = null; // use defaults
} else if (_.isType(localsOptions, 'object')) {
Locals = localsOptions.module;
delete localsOptions.module;
} else {
logger.error(
'options.locals or options.locals.module must be a Locals module',
);
return null;
}
return new Locals(localsOptions, logger);
}
var _instance = null;
Rollbar.init = function (options, client) {
if (_instance) {
return _instance.global(options).configure(options);
}
_instance = new Rollbar(options, client);
return _instance;
};
function handleUninitialized(maybeCallback) {
var message = 'Rollbar is not initialized';
logger.error(message);
if (maybeCallback) {
maybeCallback(new Error(message));
}
}
Rollbar.prototype.global = function (options) {
options = _.handleOptions(options);
// On the server we want to ignore any maxItems setting
delete options.maxItems;
this.client.global(options);
return this;
};
Rollbar.global = function (options) {
if (_instance) {
return _instance.global(options);
} else {
handleUninitialized();
}
};
Rollbar.prototype.configure = function (options, payloadData) {
var oldOptions = this.options;
var payload = {};
if (payloadData) {
payload = { payload: payloadData };
}
this.options = _.handleOptions(oldOptions, options, payload, logger);
this.options._configuredOptions = _.handleOptions(
oldOptions._configuredOptions,
options,
payload,
);
// On the server we want to ignore any maxItems setting
delete this.options.maxItems;
logger.setVerbose(this.options.verbose);
this.client.configure(options, payloadData);
this.setupUnhandledCapture();
if (this.options.locals) {
if (this.locals) {
this.locals.updateOptions(this.options.locals);
} else {
this.locals = initLocals(this.options.locals, logger);
}
}
return this;
};
Rollbar.configure = function (options, payloadData) {
if (_instance) {
return _instance.configure(options, payloadData);
} else {
handleUninitialized();
}
};
Rollbar.prototype.lastError = function () {
return this.client.lastError;
};
Rollbar.lastError = function () {
if (_instance) {
return _instance.lastError();
} else {
handleUninitialized();
}
};
Rollbar.prototype.log = function () {
var item = this._createItem(arguments);
var uuid = item.uuid;
this.client.log(item);
return { uuid: uuid };
};
Rollbar.log = function () {
if (_instance) {
return _instance.log.apply(_instance, arguments);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype.debug = function () {
var item = this._createItem(arguments);
var uuid = item.uuid;
this.client.debug(item);
return { uuid: uuid };
};
Rollbar.debug = function () {
if (_instance) {
return _instance.debug.apply(_instance, arguments);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype.info = function () {
var item = this._createItem(arguments);
var uuid = item.uuid;
this.client.info(item);
return { uuid: uuid };
};
Rollbar.info = function () {
if (_instance) {
return _instance.info.apply(_instance, arguments);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype.warn = function () {
var item = this._createItem(arguments);
var uuid = item.uuid;
this.client.warn(item);
return { uuid: uuid };
};
Rollbar.warn = function () {
if (_instance) {
return _instance.warn.apply(_instance, arguments);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype.warning = function () {
var item = this._createItem(arguments);
var uuid = item.uuid;
this.client.warning(item);
return { uuid: uuid };
};
Rollbar.warning = function () {
if (_instance) {
return _instance.warning.apply(_instance, arguments);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype.error = function () {
var item = this._createItem(arguments);
var uuid = item.uuid;
this.client.error(item);
return { uuid: uuid };
};
Rollbar.error = function () {
if (_instance) {
return _instance.error.apply(_instance, arguments);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype._uncaughtError = function () {
var item = this._createItem(arguments);
item._isUncaught = true;
var uuid = item.uuid;
this.client.error(item);
return { uuid: uuid };
};
Rollbar.prototype.critical = function () {
var item = this._createItem(arguments);
var uuid = item.uuid;
this.client.critical(item);
return { uuid: uuid };
};
Rollbar.critical = function () {
if (_instance) {
return _instance.critical.apply(_instance, arguments);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype.buildJsonPayload = function (item) {
return this.client.buildJsonPayload(item);
};
Rollbar.buildJsonPayload = function () {
if (_instance) {
return _instance.buildJsonPayload.apply(_instance, arguments);
} else {
handleUninitialized();
}
};
Rollbar.prototype.sendJsonPayload = function (jsonPayload) {
return this.client.sendJsonPayload(jsonPayload);
};
Rollbar.sendJsonPayload = function () {
if (_instance) {
return _instance.sendJsonPayload.apply(_instance, arguments);
} else {
handleUninitialized();
}
};
Rollbar.prototype.wait = function (callback) {
this.client.wait(callback);
};
Rollbar.wait = function (callback) {
if (_instance) {
return _instance.wait(callback);
} else {
var maybeCallback = _getFirstFunction(arguments);
handleUninitialized(maybeCallback);
}
};
Rollbar.prototype.errorHandler = function () {
return function (err, request, response, next) {
var cb = function (rollbarError) {
if (rollbarError) {
logger.error('Error reporting to rollbar, ignoring: ' + rollbarError);
}
return next(err, request, response);
};
if (!err) {
return next(err, request, response);
}
if (err instanceof Error) {
return this.error(err, request, cb);
}
return this.error('Error: ' + err, request, cb);
}.bind(this);
};
Rollbar.errorHandler = function () {
if (_instance) {
return _instance.errorHandler();
} else {
handleUninitialized();
}
};
Rollbar.prototype.lambdaHandler = function (handler, timeoutHandler) {
if (handler.length <= 2) {
return this.asyncLambdaHandler(handler, timeoutHandler);
}
return this.syncLambdaHandler(handler, timeoutHandler);
};
Rollbar.prototype.asyncLambdaHandler = function (handler, timeoutHandler) {
var self = this;
var _timeoutHandler = function (event, context) {
var message = 'Function timed out';
var custom = {
originalEvent: event,
originalRequestId: context.awsRequestId,
};
self.error(message, custom);
};
var shouldReportTimeouts = self.options.captureLambdaTimeouts;
return function rollbarAsyncLambdaHandler(event, context) {
return new Promise(function (resolve, reject) {
self.lambdaContext = context;
if (shouldReportTimeouts) {
var timeoutCb = (timeoutHandler || _timeoutHandler).bind(
null,
event,
context,
);
self.lambdaTimeoutHandle = setTimeout(
timeoutCb,
context.getRemainingTimeInMillis() - 1000,
);
}
handler(event, context)
.then(function (resp) {
self.wait(function () {
clearTimeout(self.lambdaTimeoutHandle);
resolve(resp);
});
})
.catch(function (err) {
self.error(err);
self.wait(function () {
clearTimeout(self.lambdaTimeoutHandle);
reject(err);
});
});
});
};
};
Rollbar.prototype.syncLambdaHandler = function (handler, timeoutHandler) {
var self = this;
var _timeoutHandler = function (event, context, _cb) {
var message = 'Function timed out';
var custom = {
originalEvent: event,
originalRequestId: context.awsRequestId,
};
self.error(message, custom);
};
var shouldReportTimeouts = self.options.captureLambdaTimeouts;
return function (event, context, callback) {
self.lambdaContext = context;
if (shouldReportTimeouts) {
var timeoutCb = (timeoutHandler || _timeoutHandler).bind(
null,
event,
context,
callback,
);
self.lambdaTimeoutHandle = setTimeout(
timeoutCb,
context.getRemainingTimeInMillis() - 1000,
);
}
try {
handler(event, context, function (err, resp) {
if (err) {
self.error(err);
}
self.wait(function () {
clearTimeout(self.lambdaTimeoutHandle);
callback(err, resp);
});
});
} catch (err) {
self.error(err);
self.wait(function () {
clearTimeout(self.lambdaTimeoutHandle);
throw err;
});
}
};
};
Rollbar.lambdaHandler = function (handler) {
if (_instance) {
return _instance.lambdaHandler(handler);
} else {
handleUninitialized();
}
};
function wrapCallback(r, f) {
return function () {
var err = arguments[0];
if (err) {
r.error(err);
}
return f.apply(this, arguments);
};
}
Rollbar.prototype.wrapCallback = function (f) {
return wrapCallback(this, f);
};
Rollbar.wrapCallback = function (f) {
if (_instance) {
return _instance.wrapCallback(f);
} else {
handleUninitialized();
}
};
Rollbar.prototype.captureEvent = function () {
var event = _.createTelemetryEvent(arguments);
return this.client.captureEvent(event.type, event.metadata, event.level);
};
Rollbar.captureEvent = function () {
if (_instance) {
return _instance.captureEvent.apply(_instance, arguments);
} else {
handleUninitialized();
}
};
/** DEPRECATED **/
Rollbar.prototype.reportMessage = function (message, level, request, callback) {
logger.log('reportMessage is deprecated');
if (_.isFunction(this[level])) {
return this[level](message, request, callback);
} else {
return this.error(message, request, callback);
}
};
Rollbar.reportMessage = function (message, level, request, callback) {
if (_instance) {
return _instance.reportMessage(message, level, request, callback);
} else {
handleUninitialized(callback);
}
};
Rollbar.prototype.reportMessageWithPayloadData = function (
message,
payloadData,
request,
callback,
) {
logger.log('reportMessageWithPayloadData is deprecated');
return this.error(message, request, payloadData, callback);
};
Rollbar.reportMessageWithPayloadData = function (
message,
payloadData,
request,
callback,
) {
if (_instance) {
return _instance.reportMessageWithPayloadData(
message,
payloadData,
request,
callback,
);
} else {
handleUninitialized(callback);
}
};
Rollbar.prototype.handleError = function (err, request, callback) {
logger.log('handleError is deprecated');
return this.error(err, request, callback);
};
Rollbar.handleError = function (err, request, callback) {
if (_instance) {
return _instance.handleError(err, request, callback);
} else {
handleUninitialized(callback);
}
};
Rollbar.prototype.handleErrorWithPayloadData = function (
err,
payloadData,
request,
callback,
) {
logger.log('handleErrorWithPayloadData is deprecated');
return this.error(err, request, payloadData, callback);
};
Rollbar.handleErrorWithPayloadData = function (
err,
payloadData,
request,
callback,
) {
if (_instance) {
return _instance.handleErrorWithPayloadData(
err,
payloadData,
request,
callback,
);
} else {
handleUninitialized(callback);
}
};
Rollbar.handleUncaughtExceptions = function (accessToken, options) {
if (_instance) {
options = options || {};
options.accessToken = accessToken;
return _instance.configure(options);
} else {
handleUninitialized();
}
};
Rollbar.handleUnhandledRejections = function (accessToken, options) {
if (_instance) {
options = options || {};
options.accessToken = accessToken;
return _instance.configure(options);
} else {
handleUninitialized();
}
};
Rollbar.handleUncaughtExceptionsAndRejections = function (
accessToken,
options,
) {
if (_instance) {
options = options || {};
options.accessToken = accessToken;
return _instance.configure(options);
} else {
handleUninitialized();
}
};
/** Internal **/
function addTransformsToNotifier(notifier) {
notifier
.addTransform(transforms.baseData)
.addTransform(transforms.handleItemWithError)
.addTransform(transforms.addBody)
.addTransform(sharedTransforms.addMessageWithError)
.addTransform(sharedTransforms.addTelemetryData)
.addTransform(transforms.addRequestData)
.addTransform(transforms.addLambdaData)
.addTransform(sharedTransforms.addConfigToPayload)
.addTransform(transforms.scrubPayload)
.addTransform(sharedTransforms.addPayloadOptions)
.addTransform(sharedTransforms.userTransform(logger))
.addTransform(sharedTransforms.addConfiguredOptions)
.addTransform(sharedTransforms.addDiagnosticKeys)
.addTransform(sharedTransforms.itemToPayload);
}
function addPredicatesToQueue(queue) {
queue
.addPredicate(sharedPredicates.checkLevel)
.addPredicate(sharedPredicates.userCheckIgnore(logger))
.addPredicate(sharedPredicates.urlIsNotBlockListed(logger))
.addPredicate(sharedPredicates.urlIsSafeListed(logger))
.addPredicate(sharedPredicates.messageIsIgnored(logger));
}
Rollbar.prototype._createItem = function (args) {
var requestKeys = ['headers', 'protocol', 'url', 'method', 'body', 'route'];
var item = _.createItem(args, logger, this, requestKeys, this.lambdaContext);
if (item.err && item.notifier.locals) {
item.localsMap = item.notifier.locals.currentLocalsMap();
}
return item;
};
function _getFirstFunction(args) {
for (var i = 0, len = args.length; i < len; ++i) {
if (_.isFunction(args[i])) {
return args[i];
}
}
return undefined;
}
Rollbar.prototype.setupUnhandledCapture = function () {
if (this.options.captureUncaught || this.options.handleUncaughtExceptions) {
this.handleUncaughtExceptions();
}
if (
this.options.captureUnhandledRejections ||
this.options.handleUnhandledRejections
) {
this.handleUnhandledRejections();
}
};
Rollbar.prototype.handleUncaughtExceptions = function () {
var exitOnUncaught = !!this.options.exitOnUncaughtException;
delete this.options.exitOnUncaughtException;
addOrReplaceRollbarHandler(
'uncaughtException',
function (err) {
if (
!this.options.captureUncaught &&
!this.options.handleUncaughtExceptions
) {
return;
}
this._uncaughtError(err, function (err) {
if (err) {
logger.error(
'Encountered error while handling an uncaught exception.',
);
logger.error(err);
}
});
if (exitOnUncaught) {
setImmediate(
function () {
this.wait(function () {
process.exit(1);
});
}.bind(this),
);
}
}.bind(this),
);
};
Rollbar.prototype.handleUnhandledRejections = function () {
addOrReplaceRollbarHandler(
'unhandledRejection',
function (reason) {
if (
!this.options.captureUnhandledRejections &&
!this.options.handleUnhandledRejections
) {
return;
}
this._uncaughtError(reason, function (err) {
if (err) {
logger.error(
'Encountered error while handling an uncaught exception.',
);
logger.error(err);
}
});
}.bind(this),
);
};
function addOrReplaceRollbarHandler(event, action) {
// We only support up to two arguments which is enough for how this is used
// rather than dealing with `arguments` and `apply`
var fn = function (a, b) {
action(a, b);
};
fn._rollbarHandler = true;
var listeners = process.listeners(event);
var len = listeners.length;
for (var i = 0; i < len; ++i) {
if (listeners[i]._rollbarHandler) {
process.removeListener(event, listeners[i]);
}
}
process.on(event, fn);
}
function RollbarError(message, nested) {
Error.call(this);
Error.captureStackTrace(this, this.constructor);
this.message = message;
this.nested = nested;
this.name = this.constructor.name;
}
util.inherits(RollbarError, Error);
Rollbar.Error = RollbarError;
Rollbar.defaultOptions = {
host: os.hostname(),
environment: process.env.NODE_ENV || 'development',
framework: 'node-js',
showReportedMessageTraces: false,
notifier: {
name: 'node_rollbar',
version: packageJson.version,
},
scrubHeaders: packageJson.defaults.server.scrubHeaders,
scrubFields: packageJson.defaults.server.scrubFields,
addRequestData: null,
reportLevel: packageJson.defaults.reportLevel,
verbose: false,
enabled: true,
transmit: true,
sendConfig: false,
includeItemsInTelemetry: false,
captureEmail: false,
captureUsername: false,
captureIp: true,
captureLambdaTimeouts: true,
ignoreDuplicateErrors: true,
scrubRequestBody: true,
autoInstrument: false,
};
module.exports = Rollbar;