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-

389 lines (316 loc) 13.4 kB
"use strict"; var opentracing = _interopRequireWildcard(require("opentracing")); 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 _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 _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } 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); } // Capture the proxied values on script load (i.e. ASAP) in case there are // multiple layers of instrumentation. var proxiedFetch; if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object' && typeof window.fetch !== 'undefined') { proxiedFetch = window.fetch; } function getCookies() { if (typeof document === 'undefined' || !document.cookie) { return null; } var cookies = document.cookie.split(';'); var data = {}; var count = 0; for (var i = 0; i < cookies.length; i++) { var parts = cookies[i].split('=', 2); if (parts.length === 2) { var key = parts[0].replace(/^\s+/, '').replace(/\s+$/, ''); data[key] = decodeURIComponent(parts[1]); try { data[key] = JSON.parse(data[key]); } catch (_ignored) { /* Ignored */ } count++; } } if (count > 0) { return data; } return null; } // Normalize the getAllResponseHeaders output function getResponseHeaders(response) { var result = {}; var entries = response.headers.entries(); for (var i = 0; i < entries.length; i++) { var pair = entries[i]; var _pair = _slicedToArray(pair, 2), key = _pair[0], val = _pair[1]; result[key] = val; } return result; } // Automatically create spans for all requests made via window.fetch. // // NOTE: this code currently works only with a single Tracer. // var InstrumentFetch = /*#__PURE__*/function () { function InstrumentFetch() { _classCallCheck(this, InstrumentFetch); this._enabled = this._isValidContext(); this._proxyInited = false; this._internalExclusions = []; this._tracer = null; this._handleOptions = this._handleOptions.bind(this); } _createClass(InstrumentFetch, [{ key: "name", value: function name() { return 'instrument_fetch'; } }, { key: "addOptions", value: function addOptions(tracerImp) { tracerImp.addOption('fetch_instrumentation', { type: 'bool', defaultValue: false }); tracerImp.addOption('fetch_url_inclusion_patterns', { type: 'array', defaultValue: [/.*/] }); tracerImp.addOption('fetch_url_exclusion_patterns', { type: 'array', defaultValue: [] }); tracerImp.addOption('fetch_url_header_inclusion_patterns', { type: 'array', defaultValue: [/.*/] }); tracerImp.addOption('fetch_url_header_exclusion_patterns', { type: 'array', defaultValue: [] }); tracerImp.addOption('include_cookies', { type: 'bool', defaultValue: true }); } }, { key: "start", value: function start(tracerImp) { if (!this._enabled) { return; } this._tracer = tracerImp; var currentOptions = tracerImp.options(); this._addServiceHostToExclusions(currentOptions); this._handleOptions({}, currentOptions); tracerImp.on('options', this._handleOptions); } }, { key: "stop", value: function stop() { if (!this._enabled) { return; } window.fetch = proxiedFetch; } /** * Respond to options changes on the Tracer. * * Note that `modified` is the options that have changed in this call, * along with their previous and new values. `current` is the full set of * current options *including* the newly modified values. */ }, { key: "_handleOptions", value: function _handleOptions(modified, current) { // Automatically add the service host itself to the list of exclusions // to avoid reporting on the reports themselves var serviceHost = modified.collector_host; if (serviceHost) { this._addServiceHostToExclusions(current); } // Set up the proxied fetch calls unless disabled if (!this._proxyInited && current.fetch_instrumentation) { this._proxyInited = true; window.fetch = this._instrumentFetch(); } } /** * Ensure that the reports to the collector don't get instrumented as well, * as that recursive instrumentation is more confusing than valuable! */ }, { key: "_addServiceHostToExclusions", value: function _addServiceHostToExclusions(opts) { if (opts.collector_host.length === 0) { return; } // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex function escapeRegExp(str) { return "".concat(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // Check against the hostname without the port as well as the canonicalized // URL may drop the standard port. var host = escapeRegExp(opts.collector_host); var port = escapeRegExp(opts.collector_port); var set = [new RegExp("^https?://".concat(host, ":").concat(port))]; if (port === '80') { set.push(new RegExp("^http://".concat(host))); } else if (port === '443') { set.push(new RegExp("^https://".concat(host))); } this._internalExclusions = set; } /** * Check preconditions for the auto-instrumentation of fetch to work properly. * There are a lot of potential JavaScript platforms. */ }, { key: "_isValidContext", value: function _isValidContext() { if (typeof window === 'undefined') { return false; } if (!window.fetch) { return false; } return true; } }, { key: "_instrumentFetch", value: function _instrumentFetch() { var self = this; var tracer = this._tracer; return function (input, init) { var request = new Request(input, init); var opts = tracer.options(); if (!self._shouldTrace(tracer, request.url)) { // eslint-disable-next-line prefer-spread return proxiedFetch(request); } var span = tracer.startSpan('fetch'); tracer.addActiveRootSpan(span); var parsed = new URL(request.url); var tags = { method: request.method, url: request.url, // NOTE: Purposefully excluding username:password from tags. // TODO: consider sanitizing URL to mask / remove that information from the trace in general hash: parsed.hash, href: parsed.href, protocol: parsed.protocol, origin: parsed.origin, host: parsed.host, hostname: parsed.hostname, port: parsed.port, pathname: parsed.pathname, search: parsed.search }; if (opts.include_cookies) { tags.cookies = getCookies(); } // Add Open-Tracing headers if (self._shouldAddHeadersToRequest(tracer, request.url)) { var headersCarrier = {}; tracer.inject(span.context(), opentracing.FORMAT_HTTP_HEADERS, headersCarrier); Object.keys(headersCarrier).forEach(function (key) { if (!request.headers.get(key)) request.headers.set(key, headersCarrier[key]); }); } span.log({ event: 'sending', method: request.method, url: request.url, openPayload: tags }); span.addTags(tags); return proxiedFetch(request).then(function (response) { if (!response.ok) { span.addTags({ error: true }); } span.log({ method: request.method, headers: getResponseHeaders(response), status: response.status, statusText: response.statusText, responseType: response.type, url: response.url }); tracer.removeActiveRootSpan(span); span.finish(); return response; })["catch"](function (e) { span.addTags({ error: true }); tracer.removeActiveRootSpan(span); span.log({ event: 'error', error: e }); span.finish(); throw e; }); }; } }, { key: "_shouldTrace", value: function _shouldTrace(tracer, url) { // This shouldn't be possible, but let's be paranoid if (!tracer || !url) { return false; } var opts = tracer.options(); if (opts.disabled) { return false; } if (this._internalExclusions.some(function (ex) { return ex.test(url); })) { return false; } if (opts.fetch_url_exclusion_patterns.some(function (ex) { return ex.test(url); })) { return false; } if (opts.fetch_url_inclusion_patterns.some(function (inc) { return inc.test(url); })) { return true; } return false; } }, { key: "_shouldAddHeadersToRequest", value: function _shouldAddHeadersToRequest(tracer, url) { // This shouldn't be possible, but let's be paranoid if (!tracer || !url) { return false; } var opts = tracer.options(); if (opts.disabled) { return false; } if (opts.fetch_url_header_exclusion_patterns.some(function (ex) { return ex.test(url); })) { return false; } if (opts.fetch_url_header_inclusion_patterns.some(function (inc) { return inc.test(url); })) { return true; } return false; } }]); return InstrumentFetch; }(); module.exports = new InstrumentFetch(); //# sourceMappingURL=instrument_fetch.js.map