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-

459 lines (378 loc) 15.3 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 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 _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 proxied = {}; if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object' && typeof window.XMLHttpRequest !== 'undefined') { proxied = { XMLHttpRequest: XMLHttpRequest, open: XMLHttpRequest.prototype.open, send: XMLHttpRequest.prototype.send, setRequestHeader: XMLHttpRequest.prototype.setRequestHeader }; } 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(xhr) { var raw = xhr.getAllResponseHeaders(); var parts = raw.replace(/\s+$/, '').split(/\n/); for (var i = 0; i < parts.length; i++) { parts[i] = parts[i].replace(/\r/g, '').replace(/^\s+/, '').replace(/\s+$/, ''); } return parts; } // Automatically create spans for all XMLHttpRequest objects. // // NOTE: this code currently works only with a single Tracer. // var InstrumentXHR = /*#__PURE__*/function () { function InstrumentXHR() { _classCallCheck(this, InstrumentXHR); this._enabled = this._isValidContext(); this._proxyInited = false; this._internalExclusions = []; this._tracer = null; this._handleOptions = this._handleOptions.bind(this); } _createClass(InstrumentXHR, [{ key: "name", value: function name() { return 'instrument_xhr'; } }, { key: "addOptions", value: function addOptions(tracerImp) { tracerImp.addOption('xhr_instrumentation', { type: 'bool', defaultValue: false }); tracerImp.addOption('xhr_url_inclusion_patterns', { type: 'array', defaultValue: [/.*/] }); tracerImp.addOption('xhr_url_exclusion_patterns', { type: 'array', defaultValue: [] }); tracerImp.addOption('xhr_url_header_inclusion_patterns', { type: 'array', defaultValue: [/.*/] }); tracerImp.addOption('xhr_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; } var proto = proxied.XMLHttpRequest.prototype; proto.open = proxied.open; proto.send = proxied.send; } /** * 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 XHR calls unless disabled if (!this._proxyInited && current.xhr_instrumentation) { this._proxyInited = true; var proto = proxied.XMLHttpRequest.prototype; proto.setRequestHeader = this._instrumentSetRequestHeader(); proto.open = this._instrumentOpen(); proto.send = this._instrumentSend(); } } /** * 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 XHRs to work properly. * There are a lot of potential JavaScript platforms. */ }, { key: "_isValidContext", value: function _isValidContext() { if (typeof window === 'undefined') { return false; } if (!window.XMLHttpRequest) { return false; } if (!window.XMLHttpRequest.prototype) { return false; } return true; } }, { key: "_instrumentSetRequestHeader", value: function _instrumentSetRequestHeader() { return function (header, value) { this.__requestHeaders = this.__requestHeaders || {}; this.__requestHeaders[header] = value; return proxied.setRequestHeader.apply(this, arguments); }; } }, { key: "_instrumentOpen", value: function _instrumentOpen() { var self = this; var tracer = this._tracer; return function (method, url, asyncArg, user, password) { if (!self._shouldTrace(tracer, this, url)) { return proxied.open.apply(this, arguments); } var opts = tracer.options(); var span = tracer.startSpan('XMLHttpRequest'); tracer.addActiveRootSpan(span); this.__tracer_span = span; this.__tracer_url = url; var tags = { method: method, url: url, async: asyncArg, user: user }; if (url) { // eslint-disable-next-line prefer-destructuring tags.url_pathname = url.split('?')[0]; } var openPayload = _objectSpread({}, tags); if (opts.include_cookies) { openPayload.cookies = getCookies(); } // Note: async defaults to true var async = asyncArg === undefined ? true : asyncArg; if (async) { this.addEventListener('readystatechange', function () { if (this.readyState === 0) { span.log({ readyState: 0, event: 'unsent' }); } else if (this.readyState === 1) { span.log({ readyState: 1, event: 'sending' }); } else if (this.readyState === 2) { span.log({ readyState: 2, event: 'headers received', method: method, url: url, openPayload: openPayload, headers: getResponseHeaders(this) }); span.addTags(tags); } else if (this.readyState === 3) { span.log({ readyState: 3, event: 'loading' }); } else if (this.readyState === 4) { var responseType = this.responseType; span.log({ readyState: 4, url: url, method: method, headers: getResponseHeaders(this), status: this.status, statusText: this.statusText, responseType: responseType }); tracer.removeActiveRootSpan(span); span.finish(); } else { span.log({ readyState: this.readyState }); } }); } var result = proxied.open.apply(this, arguments); if (!async) { tracer.removeActiveRootSpan(span); span.finish(); } return result; }; } }, { key: "_instrumentSend", value: function _instrumentSend() { var self = this; var tracer = this._tracer; return function () { var _this = this; if (!self._shouldTrace(tracer, this, this.__tracer_url)) { return proxied.send.apply(this, arguments); } var span = this.__tracer_span; if (!span) { return proxied.send.apply(this, arguments); } var data = Array.prototype.slice.call(arguments); var len; if (data.length === 1) { if (data[0] && data[0].length) { len = data[0].length; } try { data = JSON.parse(data[0]); } catch (e) {// Ignore the error } } var lenStr = len === undefined ? '' : ", data length=".concat(len); span.log({ event: 'send', data_length: lenStr }); // Add Open-Tracing headers if (self._shouldAddHeadersToRequest(tracer, this.__tracer_url)) { var headersCarrier = {}; tracer.inject(span.context(), opentracing.FORMAT_HTTP_HEADERS, headersCarrier); var keys = Object.keys(headersCarrier); keys.forEach(function (key) { proxied.setRequestHeader.call(_this, key, headersCarrier[key]); }); } return proxied.send.apply(this, arguments); }; } }, { key: "_shouldTrace", value: function _shouldTrace(tracer, xhr, 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.xhr_url_exclusion_patterns.some(function (ex) { return ex.test(url); })) { return false; } if (opts.xhr_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.xhr_url_header_exclusion_patterns.some(function (ex) { return ex.test(url); })) { return false; } if (opts.xhr_url_header_inclusion_patterns.some(function (inc) { return inc.test(url); })) { return true; } return false; } }]); return InstrumentXHR; }(); module.exports = new InstrumentXHR(); //# sourceMappingURL=instrument_xhr.js.map