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.
322 lines (268 loc) • 8.15 kB
JavaScript
import Notifier from './notifier.js';
import Queue from './queue.js';
import RateLimiter from './rateLimiter.js';
import * as _ from './utility.js';
/*
* Rollbar - the interface to Rollbar
*
* @param options
* @param api
* @param logger
*/
function Rollbar(options, api, logger, telemeter, tracing, replay, platform) {
this.options = _.merge(options);
this.logger = logger;
Rollbar.rateLimiter.configureGlobal(this.options);
Rollbar.rateLimiter.setPlatformOptions(platform, this.options);
this.api = api;
this.queue = new Queue(
Rollbar.rateLimiter,
api,
logger,
this.options,
replay,
);
this.tracing = tracing;
// Legacy OpenTracing support
// This must happen before the Notifier is created
var tracer = this.options.tracer || null;
if (validateTracer(tracer)) {
this.tracer = tracer;
// set to a string for api response serialization
this.options.tracer = 'opentracing-tracer-enabled';
this.options._configuredOptions.tracer = 'opentracing-tracer-enabled';
} else {
this.tracer = null;
}
this.notifier = new Notifier(this.queue, this.options);
this.telemeter = telemeter;
setStackTraceLimit(options);
this.lastError = null;
this.lastErrorHash = 'none';
}
var defaultOptions = {
maxItems: 0,
itemsPerMinute: 60,
};
Rollbar.rateLimiter = new RateLimiter(defaultOptions);
Rollbar.prototype.global = function (options) {
Rollbar.rateLimiter.configureGlobal(options);
return this;
};
Rollbar.prototype.configure = function (options, payloadData) {
var oldOptions = this.options;
var payload = {};
if (payloadData) {
payload = { payload: payloadData };
}
this.options = _.merge(oldOptions, options, payload);
// Legacy OpenTracing support
// This must happen before the Notifier is configured
var tracer = this.options.tracer || null;
if (validateTracer(tracer)) {
this.tracer = tracer;
// set to a string for api response serialization
this.options.tracer = 'opentracing-tracer-enabled';
this.options._configuredOptions.tracer = 'opentracing-tracer-enabled';
} else {
this.tracer = null;
}
this.notifier && this.notifier.configure(this.options);
this.telemeter && this.telemeter.configure(this.options);
setStackTraceLimit(options);
this.global(this.options);
if (validateTracer(options.tracer)) {
this.tracer = options.tracer;
}
return this;
};
Rollbar.prototype.log = function (item) {
var level = this._defaultLogLevel();
return this._log(level, item);
};
Rollbar.prototype.debug = function (item) {
this._log('debug', item);
};
Rollbar.prototype.info = function (item) {
this._log('info', item);
};
Rollbar.prototype.warn = function (item) {
this._log('warning', item);
};
Rollbar.prototype.warning = function (item) {
this._log('warning', item);
};
Rollbar.prototype.error = function (item) {
this._log('error', item);
};
Rollbar.prototype.critical = function (item) {
this._log('critical', item);
};
Rollbar.prototype.wait = function (callback) {
this.queue.wait(callback);
};
Rollbar.prototype.captureEvent = function (type, metadata, level) {
return this.telemeter && this.telemeter.captureEvent(type, metadata, level);
};
Rollbar.prototype.captureDomContentLoaded = function (ts) {
return this.telemeter && this.telemeter.captureDomContentLoaded(ts);
};
Rollbar.prototype.captureLoad = function (ts) {
return this.telemeter && this.telemeter.captureLoad(ts);
};
Rollbar.prototype.buildJsonPayload = function (item) {
return this.api.buildJsonPayload(item);
};
Rollbar.prototype.sendJsonPayload = function (jsonPayload) {
this.api.postJsonPayload(jsonPayload);
};
/* Internal */
Rollbar.prototype._log = function (defaultLevel, item) {
var callback;
if (item.callback) {
callback = item.callback;
delete item.callback;
}
if (this.options.ignoreDuplicateErrors && this._sameAsLastError(item)) {
if (callback) {
var error = new Error('ignored identical item');
error.item = item;
callback(error);
}
return;
}
try {
item.level = item.level || defaultLevel;
this._addItemAttributes(item);
// Legacy OpenTracing support
this._addTracingInfo(item);
const telemeter = this.telemeter;
if (telemeter) {
telemeter._captureRollbarItem(item);
item.telemetryEvents = telemeter.copyEvents() || [];
}
this.notifier.log(item, callback);
} catch (e) {
if (callback) {
callback(e);
}
this.logger.error(e);
}
};
Rollbar.prototype._addItemAttributes = function (item) {
const span = this.tracing?.getSpan();
const asyncLocalSessionId = _.getSessionIdFromAsyncLocalStorage(this);
const sessionId = asyncLocalSessionId || this.tracing?.sessionId;
const attributes = [
{ key: 'session_id', value: sessionId },
{ key: 'span_id', value: span?.spanId },
{ key: 'trace_id', value: span?.traceId },
];
if (item._isUncaught) {
attributes.push({ key: 'is_uncaught', value: 'true' });
}
_.addItemAttributes(item.data, attributes);
span?.addEvent('rollbar.occurrence', [
{ key: 'rollbar.occurrence.uuid', value: item.uuid },
]);
};
Rollbar.prototype._defaultLogLevel = function () {
return this.options.logLevel || 'debug';
};
Rollbar.prototype._sameAsLastError = function (item) {
if (!item._isUncaught) {
return false;
}
var itemHash = generateItemHash(item);
if (this.lastErrorHash === itemHash) {
return true;
}
this.lastError = item.err;
this.lastErrorHash = itemHash;
return false;
};
Rollbar.prototype._addTracingInfo = function (item) {
// Tracer validation occurs in the constructor
// or in the Rollbar.prototype.configure methods
if (this.tracer) {
// add rollbar occurrence uuid to span
var span = this.tracer.scope().active();
if (validateSpan(span)) {
span.setTag('rollbar.error_uuid', item.uuid);
span.setTag('rollbar.has_error', true);
span.setTag('error', true);
span.setTag(
'rollbar.item_url',
`https://rollbar.com/item/uuid/?uuid=${item.uuid}`,
);
span.setTag(
'rollbar.occurrence_url',
`https://rollbar.com/occurrence/uuid/?uuid=${item.uuid}`,
);
// add span ID & trace ID to occurrence
var opentracingSpanId = span.context().toSpanId();
var opentracingTraceId = span.context().toTraceId();
if (item.custom) {
item.custom.opentracing_span_id = opentracingSpanId;
item.custom.opentracing_trace_id = opentracingTraceId;
} else {
item.custom = {
opentracing_span_id: opentracingSpanId,
opentracing_trace_id: opentracingTraceId,
};
}
}
}
};
function generateItemHash(item) {
var message = item.message || '';
var stack = (item.err || {}).stack || String(item.err);
return message + '::' + stack;
}
// Node.js, Chrome, Safari, and some other browsers support this property
// which globally sets the number of stack frames returned in an Error object.
// If a browser can't use it, no harm done.
function setStackTraceLimit(options) {
if (options.stackTraceLimit) {
Error.stackTraceLimit = options.stackTraceLimit;
}
}
/**
* Validate the Tracer object provided to the Client
* is valid for our Opentracing use case.
* @param {opentracer.Tracer} tracer
*/
function validateTracer(tracer) {
if (!tracer) {
return false;
}
if (!tracer.scope || typeof tracer.scope !== 'function') {
return false;
}
var scope = tracer.scope();
if (!scope || !scope.active || typeof scope.active !== 'function') {
return false;
}
return true;
}
/**
* Validate the Span object provided
* @param {opentracer.Span} span
*/
function validateSpan(span) {
if (!span || !span.context || typeof span.context !== 'function') {
return false;
}
var spanContext = span.context();
if (
!spanContext ||
!spanContext.toSpanId ||
!spanContext.toTraceId ||
typeof spanContext.toSpanId !== 'function' ||
typeof spanContext.toTraceId !== 'function'
) {
return false;
}
return true;
}
export default Rollbar;