UNPKG

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
"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