lightstep-tracer
Version:
> ❗ **This instrumentation is no longer recommended**. Please review [documentation on setting up and configuring the OpenTelemetry Node.js Launcher](https://github.com/lightstep/otel-launcher-node) or [OpenTelemetry JS (Browser)](https://github.com/open-
1,415 lines (1,101 loc) • 55.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _eventemitter = _interopRequireDefault(require("eventemitter3"));
var opentracing = _interopRequireWildcard(require("opentracing"));
var _span_context_imp = _interopRequireDefault(require("./span_context_imp"));
var _span_imp = _interopRequireDefault(require("./span_imp"));
var _each2 = _interopRequireDefault(require("../_each"));
var _platform_abstraction_layer = require("../platform_abstraction_layer");
var _auth_imp = _interopRequireDefault(require("./auth_imp"));
var _runtime_imp = _interopRequireDefault(require("./runtime_imp"));
var _report_imp = _interopRequireDefault(require("./report_imp"));
var _propagator = _interopRequireDefault(require("./propagator"));
var _propagator_ls = _interopRequireDefault(require("./propagator_ls"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
var ClockState = require('./util/clock_state');
var LogBuilder = require('./log_builder');
var coerce = require('./coerce');
var constants = require('../constants');
var globals = require('./globals');
var packageObject = require('../../package.json');
var util = require('./util/util');
var DEFAULT_COLLECTOR_HOSTNAME = 'collector.lightstep.com';
var DEFAULT_COLLECTOR_PORT_TLS = 443;
var DEFAULT_COLLECTOR_PORT_PLAIN = 80;
var DEFAULT_COLLECTOR_PATH = ''; // Internal errors should be rare. Set a low limit to ensure a cascading failure
// does not compound an existing problem by trying to send a great deal of
// internal error data.
var MAX_INTERNAL_LOGS = 20;
var _singleton = null;
var Tracer = /*#__PURE__*/function (_opentracing$Tracer) {
_inherits(Tracer, _opentracing$Tracer);
var _super = _createSuper(Tracer);
function Tracer(opts) {
var _this;
_classCallCheck(this, Tracer);
_this = _super.call(this);
_this._delegateEventEmitterMethods();
opts = opts || {};
if (!_singleton) {
globals.setOptions(opts);
_singleton = _assertThisInitialized(_this);
} // Platform abstraction layer
_this._platform = new _platform_abstraction_layer.Platform(_assertThisInitialized(_this));
_this._runtimeGUID = opts.guid || _this.override_runtime_guid || null; // Set once the group name is set
_this._plugins = {};
_this._options = {};
_this._optionDescs = [];
_this._makeOptionsTable();
_this._opentracing = opentracing;
if (opts.opentracing_module) {
_this._opentracing = opts.opentracing_module;
}
var now = _this._platform.nowMicros(); // The thrift authentication and runtime struct are created as soon as
// the necessary initialization options are available.
_this._startMicros = now;
_this._auth = null;
_this._runtime = null;
var logger = {
warn: function warn(msg, payload) {
_this._warn(msg, payload);
},
error: function error(err, payload) {
_this._error(err, payload);
}
};
if (opts) {
_this._transport = opts.override_transport;
}
_this._propagators = {};
_this._propagators[_this._opentracing.FORMAT_HTTP_HEADERS] = new _propagator_ls["default"](_assertThisInitialized(_this));
_this._propagators[_this._opentracing.FORMAT_TEXT_MAP] = new _propagator_ls["default"](_assertThisInitialized(_this));
_this._propagators[_this._opentracing.FORMAT_BINARY] = new _propagator["default"](_assertThisInitialized(_this), _this._opentracing.FORMAT_BINARY);
if (opts && opts.propagators) {
_this._propagators = _objectSpread(_objectSpread({}, _this._propagators), opts.propagators);
}
_this._reportingLoopActive = false;
_this._first_report_has_run = false;
_this._reportYoungestMicros = now;
_this._reportTimer = null;
_this._reportErrorStreak = 0; // Number of consecutive errors
_this._lastVisibleErrorMillis = 0;
_this._skippedVisibleErrors = 0; // Set addActiveRootSpan() for detail
_this._activeRootSpanSet = {};
_this._activeRootSpan = null; // Span reporting buffer and per-report data
// These data are reset on every successful report.
_this._spanRecords = []; // The counter names need to match those accepted by the collector.
// These are internal counters only.
_this._counters = {
'internal.errors': 0,
'internal.warnings': 0,
'spans.dropped': 0,
'logs.dropped': 0,
'logs.keys.over_limit': 0,
'logs.values.over_limit': 0,
'reports.errors.send': 0
}; // For internal (not client) logs reported to the collector
_this._internalLogs = []; // Current runtime state / status
_this._flushIsActive = false; // Built-in plugins
_this.addPlugin(require('../plugins/log_to_console')); // Initialize the platform options after the built-in plugins in
// case any of those options affect the built-ins.
_this.addPlatformPlugins(opts);
_this.setPlatformOptions(opts); // Set constructor arguments
if (opts) {
_this.options(opts);
}
if (typeof _this._transport === 'undefined' || _this._transport === null) {
switch (_this._options.transport) {
case 'thrift':
_this._transport = new _platform_abstraction_layer.ThriftTransport(logger);
_this._info('Using thrift transport per user-defined option.');
break;
default:
_this._transport = new _platform_abstraction_layer.ThriftTransport(logger);
_this._info('Using thrift transport per user-defined option.');
}
} // For clock skew adjustment.
// Must be set after options have been set.
_this._useClockState = !_this._options.disable_clock_skew_correction;
_this._clockState = new ClockState({
nowMicros: function nowMicros() {
return _this._platform.nowMicros();
},
localStoreGet: function localStoreGet() {
var key = "clock_state/".concat(_this._options.collector_host);
return _this._platform.localStoreGet(key);
},
localStoreSet: function localStoreSet(value) {
var key = "clock_state/".concat(_this._options.collector_host);
return _this._platform.localStoreSet(key, value);
}
}); // This relies on the options being set: call this last.
_this._setupReportOnExit();
_this._info("Tracer created with guid ".concat(_this._runtimeGUID));
if (_this._options.access_token.length === 0) {
_this._warn("Access token not set -\n this requires a satellite with access token checking disabled,\n such as a developer satellite.");
}
_this.startPlugins();
return _this;
} // Morally speaking, Tracer also inherits from EventEmmiter, but we must
// fake it via composition.
//
// If not obvious on inspection: a hack.
_createClass(Tracer, [{
key: "_delegateEventEmitterMethods",
value: function _delegateEventEmitterMethods() {
var self = this;
this._ee = new _eventemitter["default"](); // The list of methods at https://nodejs.org/api/events.html
(0, _each2["default"])(['addListener', 'emit', 'eventNames', 'getMaxListeners', 'listenerCount', 'listeners', 'on', 'once', 'prependListener', 'prependOnceListener', 'removeAllListeners', 'removeListener', 'setMaxListeners'], function (methodName) {
self[methodName] = function () {
if (self._ee[methodName]) {
// eslint-disable-next-line prefer-spread
self._ee[methodName].apply(self._ee, arguments);
}
};
});
}
}, {
key: "_makeOptionsTable",
value: function _makeOptionsTable() {
/* eslint-disable key-spacing, no-multi-spaces */
// NOTE: make 'verbosity' the first option so it is processed first on
// options changes and takes effect as soon as possible.
this.addOption('verbosity', {
type: 'int',
min: 0,
max: 9,
defaultValue: 1
}); // Core options
this.addOption('access_token', {
type: 'string',
defaultValue: ''
});
this.addOption('component_name', {
type: 'string',
defaultValue: ''
});
this.addOption('collector_host', {
type: 'string',
defaultValue: DEFAULT_COLLECTOR_HOSTNAME
});
this.addOption('collector_port', {
type: 'int',
defaultValue: DEFAULT_COLLECTOR_PORT_TLS
});
this.addOption('collector_path', {
type: 'string',
defaultValue: DEFAULT_COLLECTOR_PATH
});
this.addOption('collector_encryption', {
type: 'string',
defaultValue: 'tls'
});
this.addOption('tags', {
type: 'any',
defaultValue: {}
});
this.addOption('max_reporting_interval_millis', {
type: 'int',
defaultValue: 2500
});
this.addOption('disable_clock_skew_correction', {
type: 'bool',
defaultValue: false
});
this.addOption('transport', {
type: 'string',
defaultValue: 'thrift'
}); // Non-standard, may be deprecated
this.addOption('disabled', {
type: 'bool',
defaultValue: false
});
this.addOption('max_span_records', {
type: 'int',
defaultValue: 4096
});
this.addOption('default_span_tags', {
type: 'any',
defaultValue: {}
});
this.addOption('report_timeout_millis', {
type: 'int',
defaultValue: 30000
});
this.addOption('gzip_json_requests', {
type: 'bool',
defaultValue: true
});
this.addOption('disable_reporting_loop', {
type: 'bool',
defaultValue: false
});
this.addOption('disable_report_on_exit', {
type: 'bool',
defaultValue: false
});
this.addOption('delay_initial_report_millis', {
type: 'int',
defaultValue: 1000
});
this.addOption('error_throttle_millis', {
type: 'int',
defaultValue: 60000
});
this.addOption('logger', {
type: 'function',
defaultValue: this._printToConsole.bind(this)
});
this.addOption('clear_span_buffer_consecutive_errors', {
type: 'int',
defaultValue: null
}); // Debugging options
//
// These are not part of the supported public API.
//
// If false, SSL certificate verification is skipped. Useful for testing.
this.addOption('certificate_verification', {
type: 'bool',
defaultValue: true
}); // I.e. report only on explicit calls to flush()
// Unit testing options
this.addOption('override_transport', {
type: 'any',
defaultValue: null
});
this.addOption('silent', {
type: 'bool',
defaultValue: false
}); // Hard upper limits to protect against worst-case scenarios for log field sizes.
this.addOption('log_field_key_hard_limit', {
type: 'int',
defaultValue: 256
});
this.addOption('log_field_value_hard_limit', {
type: 'int',
defaultValue: 1024
}); // Meta Event reporting options
this.addOption('disable_meta_event_reporting', {
type: 'bool',
defaultValue: false
});
/* eslint-disable key-spacing, no-multi-spaces */
} // ---------------------------------------------------------------------- //
// opentracing.Tracer SPI
// ---------------------------------------------------------------------- //
}, {
key: "_startSpan",
value: function _startSpan(name, fields) {
var _this2 = this;
// First, assemble the SpanContextImp for our SpanImp.
var parentCtxImp = null;
fields = fields || {};
if (fields.references) {
for (var i = 0; i < fields.references.length; i++) {
var ref = fields.references[i];
var type = ref.type();
if (type === this._opentracing.REFERENCE_CHILD_OF || type === this._opentracing.REFERENCE_FOLLOWS_FROM) {
var context = ref.referencedContext();
if (!context) {
this._error('Span reference has an invalid context', context); // eslint-disable-next-line no-continue
continue;
}
parentCtxImp = context;
break;
}
}
}
var traceGUID = parentCtxImp ? parentCtxImp.traceGUID() : this.generateTraceGUIDForRootSpan();
var sampled = parentCtxImp ? parentCtxImp._sampled : true;
var spanCtx = new _span_context_imp["default"](this._platform.generateUUID(), traceGUID, sampled);
var spanImp = new _span_imp["default"](this, name, spanCtx);
spanImp.addTags(this._options.default_span_tags);
(0, _each2["default"])(fields, function (value, key) {
switch (key) {
case 'references':
// Ignore: handled before constructing the span
break;
case 'startTime':
// startTime is in milliseconds
spanImp.setBeginMicros(Math.floor(value * 1000));
break;
case 'tags':
spanImp.addTags(value);
break;
default:
_this2._warn("Ignoring unknown field '".concat(key, "'"));
break;
}
});
if (parentCtxImp !== null) {
spanImp.setParentGUID(parentCtxImp._guid); // Copy baggage items from parent to child
parentCtxImp.forEachBaggageItem(function (k, v) {
return spanCtx.setBaggageItem(k, v);
});
}
this.emit('start_span', spanImp);
if (util.shouldSendMetaSpan(this.options(), spanImp.getTags())) {
var _tags;
this.startSpan(constants.LS_META_SP_START, {
tags: (_tags = {}, _defineProperty(_tags, constants.LS_META_EVENT_KEY, true), _defineProperty(_tags, constants.LS_META_TRACE_KEY, spanImp.traceGUID()), _defineProperty(_tags, constants.LS_META_SPAN_KEY, spanImp.guid()), _tags)
}).finish();
}
return spanImp;
}
}, {
key: "_inject",
value: function _inject(spanContext, format, carrier) {
if (this.options().meta_event_reporting === true) {
var _tags2;
this.startSpan(constants.LS_META_INJECT, {
tags: (_tags2 = {}, _defineProperty(_tags2, constants.LS_META_EVENT_KEY, true), _defineProperty(_tags2, constants.LS_META_TRACE_KEY, spanContext._traceGUID), _defineProperty(_tags2, constants.LS_META_SPAN_KEY, spanContext._guid), _defineProperty(_tags2, constants.LS_META_PROPAGATION_KEY, format), _tags2)
}).finish();
}
switch (format) {
case this._opentracing.FORMAT_HTTP_HEADERS:
this._propagators[this._opentracing.FORMAT_HTTP_HEADERS].inject(spanContext, carrier);
break;
case this._opentracing.FORMAT_TEXT_MAP:
this._propagators[this._opentracing.FORMAT_TEXT_MAP].inject(spanContext, carrier);
break;
case this._opentracing.FORMAT_BINARY:
this._propagators[this._opentracing.FORMAT_BINARY].inject(spanContext, carrier);
break;
default:
this._error("Unknown format: ".concat(format));
break;
}
}
}, {
key: "_extract",
value: function _extract(format, carrier) {
var sc = null;
switch (format) {
case this._opentracing.FORMAT_HTTP_HEADERS:
sc = this._propagators[this._opentracing.FORMAT_HTTP_HEADERS].extract(carrier);
break;
case this._opentracing.FORMAT_TEXT_MAP:
sc = this._propagators[this._opentracing.FORMAT_TEXT_MAP].extract(carrier);
break;
case this._opentracing.FORMAT_BINARY:
sc = this._propagators[this._opentracing.FORMAT_BINARY].extract(carrier);
break;
default:
this._error("Unsupported format: ".concat(format));
return null;
}
if (this.options().meta_event_reporting === true && sc) {
var _tags3;
this.startSpan(constants.LS_META_EXTRACT, {
tags: (_tags3 = {}, _defineProperty(_tags3, constants.LS_META_EVENT_KEY, true), _defineProperty(_tags3, constants.LS_META_TRACE_KEY, sc._traceGUID), _defineProperty(_tags3, constants.LS_META_SPAN_KEY, sc._guid), _defineProperty(_tags3, constants.LS_META_PROPAGATION_KEY, format), _tags3)
}).finish();
}
return sc;
} // ---------------------------------------------------------------------- //
// LightStep extensions
// ---------------------------------------------------------------------- //
/**
* Manually sends a report of all buffered data.
*
* @param {Function} done - callback function invoked when the report
* either succeeds or fails.
*/
}, {
key: "flush",
value: function flush(done) {
if (!done) {
done = function done() {};
}
if (this._options.disabled) {
this._warn('Manual flush() called in disabled state.');
return done(null);
}
this._flushReport(true, false, done);
} //-----------------------------------------------------------------------//
// Options
//-----------------------------------------------------------------------//
}, {
key: "guid",
value: function guid() {
return this._runtimeGUID;
}
}, {
key: "verbosity",
value: function verbosity() {
// The 'undefined' handling below is for logs that may occur before the
// options are initialized.
var v = this._options.verbosity;
return v === undefined ? 1 : v;
} // Call to generate a new Trace GUID
}, {
key: "generateTraceGUIDForRootSpan",
value: function generateTraceGUIDForRootSpan() {
var guid = this._platform.generateUUID();
if (this._activeRootSpan) {
guid = this._activeRootSpan.traceGUID();
}
return guid;
}
}, {
key: "setPlatformOptions",
value: function setPlatformOptions(userOptions) {
var opts = this._platform.options(this) || {};
(0, _each2["default"])(userOptions, function (val, key) {
opts[key] = val;
});
this.options(opts);
} // Register a new option. Used by plug-ins.
}, {
key: "addOption",
value: function addOption(name, desc) {
desc.name = name;
this._optionDescs.push(desc);
this._options[desc.name] = desc.defaultValue;
}
}, {
key: "options",
value: function options(opts) {
var _this3 = this;
if (arguments.length === 0) {
console.assert(_typeof(this._options) === 'object', // eslint-disable-line
'Internal error: _options field incorrect');
return this._options;
}
if (_typeof(opts) !== 'object') {
throw new Error("options() must be called with an object: type was ".concat(_typeof(opts)));
} // "collector_port" 0 acts as an alias for "use the default".
if (opts.collector_port === 0) {
delete opts.collector_port;
} // "collector_encryption" acts an alias for the common cases of 'collector_port'
if (opts.collector_encryption !== undefined && opts.collector_port === undefined) {
opts.collector_port = opts.collector_encryption !== 'none' ? DEFAULT_COLLECTOR_PORT_TLS : DEFAULT_COLLECTOR_PORT_PLAIN;
} // set meta event reporting to false by default, it will be enabled by the satellite
this.meta_event_reporting = false; // Track what options have been modified
var modified = {};
var unchanged = {};
(0, _each2["default"])(this._optionDescs, function (desc) {
_this3._setOptionInternal(modified, unchanged, opts, desc);
}); // Check for any invalid options: is there a key in the specified operation
// that didn't result either in a change or a reset to the existing value?
Object.keys(opts).forEach(function (key) {
if (modified[key] === undefined && unchanged[key] === undefined) {
_this3._warn("Invalid option ".concat(key, " with value ").concat(opts[key]));
}
}); //
// Update the state information based on the changes
//
this._initReportingDataIfNeeded(modified);
if (!this._reportingLoopActive) {
this._startReportingLoop();
}
if (this.verbosity() >= 3) {
var optionsString = '';
var count = 0;
(0, _each2["default"])(modified, function (val, key) {
optionsString += "\t".concat(JSON.stringify(key), ": ").concat(JSON.stringify(val.newValue), "\n");
count++;
});
if (count > 0) {
this._debug("Options modified:\n".concat(optionsString));
}
}
this.emit('options', modified, this._options, this);
}
}, {
key: "_setOptionInternal",
value: function _setOptionInternal(modified, unchanged, opts, desc) {
var name = desc.name;
var value = opts[name];
var valueType = _typeof(value);
if (value === undefined) {
return;
} // Parse the option (and check constraints)
switch (desc.type) {
case 'any':
break;
case 'bool':
if (value !== true && value !== false) {
this._error("Invalid boolean option '".concat(name, "' '").concat(value, "'"));
return;
}
break;
case 'function':
if (typeof value !== 'function') {
this._error("Invalid function option '".concat(name, "' '").concat(value, "'"));
return;
}
break;
case 'int':
if (valueType !== 'number' || Math.floor(value) !== value) {
this._error("Invalid int option '".concat(name, "' '").concat(value, "'"));
return;
}
if (desc.min !== undefined && desc.max !== undefined) {
if (!(value >= desc.min && value <= desc.max)) {
this._error("Option '".concat(name, "' out of range '").concat(value, "' is not between ").concat(desc.min, " and ").concat(desc.max)); // eslint-disable-line max-len
return;
}
}
break;
case 'string':
switch (valueType) {
case 'string':
break;
case 'number':
value = coerce.toString(value);
break;
default:
this._error("Invalid string option ".concat(name, " ").concat(value));
return;
}
break;
case 'array':
// Per http://stackoverflow.com/questions/4775722/check-if-object-is-array
if (Object.prototype.toString.call(value) !== '[object Array]') {
this._error("Invalid type for array option ".concat(name, ": found '").concat(valueType, "'"));
return;
}
break;
default:
this._error("Unknown option type '".concat(desc.type, "'"));
return;
} // Set the new value, recording any modifications
var oldValue = this._options[name];
if (oldValue === undefined) {
throw new Error("Attempt to set unknown option ".concat(name));
} // Ignore no-op changes for types that can be checked quickly
if (valueType !== 'object' && oldValue === value) {
unchanged[name] = true;
return;
}
modified[name] = {
oldValue: oldValue,
newValue: value
};
this._options[name] = value;
} // The authorization and runtime information is initialized as soon
// as it is available. This allows logs and spans to be buffered before
// the library is initialized, which can be helpul in a complex setup with
// many subsystems.
//
}, {
key: "_initReportingDataIfNeeded",
value: function _initReportingDataIfNeeded(modified) {
var _this4 = this;
// Ignore redundant initialization; complaint on inconsistencies
if (this._auth !== null) {
if (!this._runtime) {
return this._error('Inconsistent state: auth initialized without runtime.');
}
if (modified.access_token) {
throw new Error('Cannot change access_token after it has been set.');
}
if (modified.component_name) {
throw new Error('Cannot change component_name after it has been set.');
}
if (modified.collector_host) {
throw new Error('Cannot change collector_host after the connection is established');
}
if (modified.collector_port) {
throw new Error('Cannot change collector_port after the connection is established');
}
if (modified.collector_path) {
throw new Error('Cannot change collector_path after the connection is established');
}
if (modified.collector_encryption) {
throw new Error('Cannot change collector_encryption after the connection is established');
}
return;
}
this._runtimeGUID = this._platform.runtimeGUID(this._options.component_name);
this._auth = new _auth_imp["default"](this._options.access_token); //
// Assemble the tracer tags from the user-specified and automatic,
// internal tags.
//
var tags = {};
(0, _each2["default"])(this._options.tags, function (value, key) {
if (typeof value !== 'string') {
_this4._error("Tracer tag value is not a string: key=".concat(key));
return;
}
tags[key] = value;
});
tags['lightstep.tracer_version'] = packageObject.version;
var platformTags = this._platform.tracerTags();
(0, _each2["default"])(platformTags, function (val, key) {
tags[key] = val;
});
this._runtime = new _runtime_imp["default"](this._runtimeGUID, this._startMicros, this._options.component_name, tags);
this._info('Initializing reporting data', {
component_name: this._options.component_name,
access_token: this._auth.getAccessToken()
});
this.emit('reporting_initialized');
}
}, {
key: "getLogFieldKeyHardLimit",
value: function getLogFieldKeyHardLimit() {
return this._options.log_field_key_hard_limit;
}
}, {
key: "getLogFieldValueHardLimit",
value: function getLogFieldValueHardLimit() {
return this._options.log_field_value_hard_limit;
} //-----------------------------------------------------------------------//
// Plugins
//-----------------------------------------------------------------------//
}, {
key: "addPlatformPlugins",
value: function addPlatformPlugins(opts) {
var _this5 = this;
var pluginSet = this._platform.plugins(opts);
(0, _each2["default"])(pluginSet, function (val) {
_this5.addPlugin(val);
});
}
}, {
key: "addPlugin",
value: function addPlugin(plugin) {
// Don't add plug-ins twice
var name = plugin.name();
if (this._plugins[name]) {
return;
}
this._plugins[name] = plugin;
plugin.addOptions(this);
}
}, {
key: "startPlugins",
value: function startPlugins() {
var _this6 = this;
(0, _each2["default"])(this._plugins, function (val, key) {
_this6._plugins[key].start(_this6);
});
} //-----------------------------------------------------------------------//
// Spans
//-----------------------------------------------------------------------//
// This is a LightStep-specific feature that should be used sparingly. It
// sets a "global" root span such that spans that would *otherwise* be root
// span instead inherit the trace GUID of the active root span. This is
// best clarified by example:
//
// On document load in the browser, an "active root span" is created for
// the page load process. Any spans started without an explicit parent
// will the document load trace GUID instead of starting a trace GUID.
// This implicit root remains active only until the page is done loading.
//
// Any span adding itself as a root span *must* remove itself along with
// calling finish(). This will *not* be done automatically.
//
// NOTE: currently, only the trace GUID is transferred; it may or may not
// make sure to make this a proper parent.
//
// NOTE: the root span tracking is handled as a set rather than a single
// global to avoid conflicts between libraries.
}, {
key: "addActiveRootSpan",
value: function addActiveRootSpan(span) {
this._activeRootSpanSet[span._guid] = span;
this._setActiveRootSpanToYoungest();
}
}, {
key: "removeActiveRootSpan",
value: function removeActiveRootSpan(span) {
delete this._activeRootSpanSet[span._guid];
this._setActiveRootSpanToYoungest();
}
}, {
key: "_setActiveRootSpanToYoungest",
value: function _setActiveRootSpanToYoungest() {
var _this7 = this;
// Set the _activeRootSpan to the youngest of the roots in case of
// multiple.
this._activeRootSpan = null;
(0, _each2["default"])(this._activeRootSpanSet, function (span) {
if (!_this7._activeRootSpan || span._beginMicros > _this7._activeRootSpan._beginMicros) {
_this7._activeRootSpan = span;
}
});
} //-----------------------------------------------------------------------//
// Encoding / decoding
//-----------------------------------------------------------------------//
}, {
key: "_objectToUint8Array",
value: function _objectToUint8Array(obj) {
var jsonString;
try {
// encodeURIComponent() is a *very* inefficient, but simple and
// well-supported way to avoid having to think about Unicode in
// in the conversion to a UInt8Array.
//
// Writing multiple bytes for String.charCodeAt and
// String.codePointAt would be an alternate approach; again,
// simplicitly is being preferred over efficiency for the moment.
jsonString = encodeURIComponent(JSON.stringify(obj));
} catch (e) {
this._error('Could not binary encode carrier data.');
return null;
}
var buffer = new ArrayBuffer(jsonString.length);
var view = new Uint8Array(buffer);
for (var i = 0; i < jsonString.length; i++) {
var code = jsonString.charCodeAt(i);
if (!(code >= 0 && code <= 255)) {
this._error('Unexpected character code');
return null;
}
view[i] = code;
}
return view;
}
}, {
key: "_uint8ArrayToObject",
value: function _uint8ArrayToObject(arr) {
if (!arr) {
this._error('Array is null');
return null;
}
var jsonString = '';
for (var i = 0; i < arr.length; i++) {
jsonString += String.fromCharCode(arr[i]);
}
try {
return JSON.parse(decodeURIComponent(jsonString));
} catch (e) {
this._error('Could not decode binary data.');
return null;
}
} //-----------------------------------------------------------------------//
// Logging
//-----------------------------------------------------------------------//
}, {
key: "log",
value: function log() {
var b = new LogBuilder(this);
return b;
} //-----------------------------------------------------------------------//
// Buffers
//-----------------------------------------------------------------------//
}, {
key: "_clearBuffers",
value: function _clearBuffers() {
this._spanRecords = [];
this._internalLogs = []; // Create a new object to avoid overwriting the values in any references
// to the old object
var counters = {};
(0, _each2["default"])(this._counters, function (unused, key) {
counters[key] = 0;
});
this._counters = counters;
}
}, {
key: "_buffersAreEmpty",
value: function _buffersAreEmpty() {
if (this._spanRecords.length > 0) {
return false;
}
if (this._internalLogs.length > 0) {
return false;
}
var countersAllZero = true;
(0, _each2["default"])(this._counters, function (val) {
if (val > 0) {
countersAllZero = false;
}
});
return countersAllZero;
}
}, {
key: "_addSpanRecord",
value: function _addSpanRecord(record) {
this._internalAddSpanRecord(record);
this.emit('span_added', record);
}
}, {
key: "_internalAddSpanRecord",
value: function _internalAddSpanRecord(record) {
if (!record) {
this._error('Attempt to add null record to buffer');
return;
}
if (this._spanRecords.length >= this._options.max_span_records) {
var index = Math.floor(this._spanRecords.length * Math.random());
this._spanRecords[index] = record;
this._counters['spans.dropped']++;
} else {
this._spanRecords.push(record);
}
}
}, {
key: "_restoreRecords",
value: function _restoreRecords(spans, internalLogs, counters) {
var _this8 = this;
(0, _each2["default"])(spans, function (span) {
_this8._internalAddSpanRecord(span);
});
var currentInternalLogs = this._internalLogs;
this._internalLogs = [];
var toAdd = internalLogs.concat(currentInternalLogs);
(0, _each2["default"])(toAdd, function (log) {
_this8._pushInternalLog(log);
});
(0, _each2["default"])(counters, function (value, key) {
if (key in _this8._counters) {
_this8._counters[key] += value;
} else {
_this8._error("Bad counter name: ".concat(key));
}
});
}
/**
* clearSpanRecordsIfMaxErrors checks to see if the tracer was configured to
* empty the span buffer after a fixed amount of errors. If it is configured,
* and there has been an error streak equal to the configured value,
* it will empty spanRecords and record that the spans were dropped.
*
* @private
*/
}, {
key: "_clearSpanRecordsIfMaxErrors",
value: function _clearSpanRecordsIfMaxErrors() {
var maxErrorsToEmpty = this.options().clear_span_buffer_consecutive_errors;
if (maxErrorsToEmpty === null || this._reportErrorStreak < maxErrorsToEmpty) {
return;
} // spanRecords is configured to be emptied
// the number of dropped spans and reporting errors should still be maintained since
// the loop may still in the process of a report.
var numSpansToDrop = this._spanRecords.length;
this._counters['spans.dropped'] += numSpansToDrop;
this._spanRecords = [];
this._warn('Span buffer flushed, max consecutive errors reached', {
max_consecutive_errors: maxErrorsToEmpty,
spans_dropped: numSpansToDrop
});
} //-----------------------------------------------------------------------//
// Reporting loop
//-----------------------------------------------------------------------//
}, {
key: "_setupReportOnExit",
value: function _setupReportOnExit() {
var _this9 = this;
if (this._options.disable_report_on_exit) {
this._debug('report-on-exit is disabled.');
return;
} // Do a final explicit flush. Note that the final flush may enqueue
// asynchronous callbacks that cause the 'beforeExit' event to be
// re-emitted when those callbacks finish.
var finalFlushOnce = 0;
var finalFlush = function finalFlush() {
if (finalFlushOnce++ > 0) {
return;
}
_this9._info('Final flush before exit.');
_this9._flushReport(false, true, function (err) {
if (err) {
_this9._warn('Final report before exit failed', {
error: err,
unflushed_spans: _this9._spanRecords.length,
buffer_youngest_micros: _this9._reportYoungestMicros
});
}
});
};
this._platform.onBeforeExit(finalFlush);
}
}, {
key: "_startReportingLoop",
value: function _startReportingLoop() {
var _this10 = this;
if (this._options.disabled) {
this._info('Not starting reporting loop: instrumentation is disabled.');
return;
}
if (this._options.disable_reporting_loop) {
this._info('Not starting reporting loop: reporting loop is disabled.');
return;
}
if (this._auth === null) {
// Don't start the loop until the thrift data necessary to do the
// report is set up.
return;
}
if (this._reportingLoopActive) {
this._info('Reporting loop already started!');
return;
}
this._info('Starting reporting loop:', this._runtime);
this._reportingLoopActive = true; // Stop the reporting loop so the Node.js process does not become a
// zombie waiting for the timers.
var stopReportingOnce = 0;
var stopReporting = function stopReporting() {
if (stopReportingOnce++ > 0) {
return;
}
_this10._stopReportingLoop();
};
this._platform.onBeforeExit(stopReporting); // Begin the asynchronous reporting loop
var loop = function loop() {
_this10._enqueueNextReport(function (err) {
if (_this10._reportingLoopActive) {
loop();
}
});
};
var delay = Math.floor(Math.random() * this._options.delay_initial_report_millis);
util.detachedTimeout(function () {
loop();
}, delay);
}
}, {
key: "_stopReportingLoop",
value: function _stopReportingLoop() {
this._debug('Stopping reporting loop');
this._reportingLoopActive = false;
clearTimeout(this._reportTimer);
this._reportTimer = null;
}
}, {
key: "_enqueueNextReport",
value: function _enqueueNextReport(done) {
var _this11 = this;
// If there's already a report request enqueued, ignore this new
// request.
if (this._reportTimer) {
return;
} // If the clock state is still being primed, potentially use the
// shorted report interval.
//
// However, do not use the shorter interval in the case of an error.
// That does not provide sufficient backoff.
var reportInterval = this._options.max_reporting_interval_millis;
if (this._reportErrorStreak === 0 && this._useClockState && !this._clockState.isReady()) {
reportInterval = Math.min(constants.CLOCK_STATE_REFRESH_INTERVAL_MS, reportInterval);
} // After 3 consecutive errors, expand the retry delay up to 8x the
// normal interval, jitter the delay by +/- 25%, and be sure to back off
// *at least* the standard reporting interval in the case of an error.
var backOff = 1 + Math.min(7, Math.max(0, this._reportErrorStreak));
var basis = backOff * reportInterval;
var jitter = 1.0 + (Math.random() * 0.5 - 0.25);
var delay = Math.floor(Math.max(0, jitter * basis));
this._debug("Delaying next flush for ".concat(delay, "ms"));
this._reportTimer = util.detachedTimeout(function () {
_this11._reportTimer = null;
_this11._flushReport(false, false, done);
}, delay);
}
/**
* Internal worker for a flush of buffered data into a report.
*
* @param {bool} manual - this is a manually invoked flush request. Don't
* override this call with a clock state syncing flush, for example.
* @param {bool} detached - this is an "at exit" flush that should not block
* the calling process in any manner. This is specifically called
* "detached" due to the browser use case where the report is done,
* not just asynchronously, but as a script request that continues
* to run even if the page is navigated away from mid-request.
* @param {function} done - standard callback function called on success
* or error.
*/
}, {
key: "_flushReport",
value: function _flushReport(manual, detached, done) {
var _this12 = this;
done = done || function (err) {};
var clockReady = this._clockState.isReady();
var clockOffsetMicros = this._clockState.offsetMicros(); // Diagnostic information on the clock correction
this._debug('time correction state', {
offset_micros: clockOffsetMicros,
active_samples: this._clockState.activeSampleCount(),
ready: clockReady
});
var spanRecords = this._spanRecords;
var counters = this._counters;
var internalLogs = this._internalLogs; // If the clock is not ready, do an "empty" flush to build more clock
// samples before the real data is reported.
// A detached flush (i.e. one intended to fire at exit or other "last
// ditch effort" event) should always use the real data.
if (this._useClockState && !manual && !clockReady && !detached) {
this._debug('Flushing empty report to prime clock state');
spanRecords = [];
counters = {};
internalLogs = [];
} else {
// Early out if we can.
if (this._buffersAreEmpty()) {
this._debug('Skipping empty report');
return done(null);
} // Clear the object buffers as the data is now in the local
// variables
this._clearBuffers();
this._debug("Flushing report (".concat(spanRecords.length, " spans)"));
}
this._transport.ensureConnection(this._options); // Ensure the runtime GUID is set as it is possible buffer logs and
// spans before the GUID is necessarily set.
console.assert(this._runtimeGUID !== null, 'No runtime GUID for Tracer'); // eslint-disable-line no-console
var timestampOffset = this._useClockState ? clockOffsetMicros : 0;
var now = this._platform.nowMicros();
var report = new _report_imp["default"](this._runtime, this._reportYoungestMicros, now, spanRecords, internalLogs, counters, timestampOffset);
this.emit('prereport', report);
var originMicros = this._platform.nowMicros();
if (this._options.meta_event_reporting && !this._first_report_has_run) {
var _tags4;
this._first_report_has_run = true;
this.startSpan(constants.LS_META_TRACER_CREATE, {
tags: (_tags4 = {}, _defineProperty(_tags4, constants.LS_META_EVENT_KEY, true), _defineProperty(_tags4, constants.LS_META_TRACER_GUID_KEY, this._runtimeGUID), _tags4)
}).finish();
}
this._transport.report(detached, this._auth, report, function (err, res) {
var destinationMicros = _this12._platform.nowMicros();
var reportWindowSeconds = (now - report.oldest_micros) / 1e6;
if (err) {
// How many errors in a row? Influences the report backoff.
_this12._reportErrorStreak++; // On a failed report, re-enqueue the data that was going to be
// sent.
var errString;
if (err.message) {
errString = "".concat(err.message);
} else {
errString = "".concat(err);
}
_this12._warn("Error in report: ".concat(errString), {
last_report_seconds_ago: reportWindowSeconds
});
_this12._restoreRecords(report.getSpanRecords(), report.getInternalLogs(), report.getCounters()); // Increment the counter *after* the counters are restored
_this12._counters['reports.errors.send']++;
_this12._clearSpanRecordsIfMaxErrors();
_this12.emit('report_error', err, {
error: err,
streak: _this12._reportErrorStreak,
detached: detached
});
} else {
if (_this12.verbosity() >= 4) {
_this12._debug("Report flushed for last ".concat(reportWindowSeconds, " seconds"), {
spans_reported: report.getSpanRecords().length
});
} // Update internal data after the successful report
_this12._reportErrorStreak = 0;
_this12._reportYoungestMicros = now; // Update the clock state if there's info from the report
if (res) {
if (res.timing && res.timing.receive_micros && res.timing.transmit_micros) {
// Handle thrift transport timing response.
_this12._clockState.addSample(origi