var RateLimiter = require('./rateLimiter'); var Queue = require('./queue'); var Notifier = require('./notifier'); var _ = require('./utility'); /* * Rollbar - the interface to Rollbar * * @param options * @param api * @param logger */ function Rollbar(options, api, logger, telemeter, 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); // 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); // 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 { this._addTracingInfo(item); item.level = item.level || defaultLevel; this.telemeter && this.telemeter._captureRollbarItem(item); item.telemetryEvents = (this.telemeter && this.telemeter.copyEvents()) || []; this.notifier.log(item, callback); } catch (e) { if (callback) { callback(e); } this.logger.error(e); } }; 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; } module.exports = Rollbar;