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-

372 lines (304 loc) 14.2 kB
"use strict"; var opentracing = _interopRequireWildcard(require("opentracing")); var _http = _interopRequireDefault(require("http")); var _https = _interopRequireDefault(require("https")); var _url = _interopRequireWildcard(require("url")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 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 _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 _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; } // Capture the proxied values on script load (i.e. ASAP) in case there are // multiple layers of instrumentation. var proxiedHttpRequest; var proxiedHttpsRequest; var proxiedHttpGet; var proxiedHttpsGet; if (typeof window === 'undefined') { proxiedHttpRequest = _http["default"].request; proxiedHttpGet = _http["default"].get; proxiedHttpsRequest = _https["default"].request; proxiedHttpsGet = _https["default"].get; } // taken from following // https://github.com/nodejs/node/blob/8507485fb242dfcaf07092414871aa9c185a28e4/lib/internal/url.js#L1254-L1276 // Utility function that converts a URL object into an ordinary // options object as expected by the http.request and https.request // APIs. function urlToOptions(url) { var options = { protocol: url.protocol, hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[') ? url.hostname.slice(1, -1) : url.hostname, hash: url.hash, search: url.search, pathname: url.pathname, path: "".concat(url.pathname || '').concat(url.search || ''), href: url.href }; if (url.port !== '') { options.port = Number(url.port); } if (url.username || url.password) { options.auth = "".concat(url.username, ":").concat(url.password); } return options; } // Automatically create spans for all requests made via window.fetch. // // NOTE: this code currently works only with a single Tracer. // var InstrumentNodejs = /*#__PURE__*/function () { function InstrumentNodejs() { _classCallCheck(this, InstrumentNodejs); this._enabled = this._isValidContext(); this._proxyInited = false; this._internalExclusions = []; this._tracer = null; this._handleOptions = this._handleOptions.bind(this); } _createClass(InstrumentNodejs, [{ key: "name", value: function name() { return 'instrument_nodejs'; } }, { key: "addOptions", value: function addOptions(tracerImp) { tracerImp.addOption('nodejs_instrumentation', { type: 'bool', defaultValue: false }); tracerImp.addOption('nodejs_url_inclusion_patterns', { type: 'array', defaultValue: [/.*/] }); tracerImp.addOption('nodejs_url_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; } _http["default"].request = proxiedHttpRequest; _http["default"].get = proxiedHttpGet; _https["default"].request = proxiedHttpsRequest; _https["default"].get = proxiedHttpsGet; } /** * 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.nodejs_instrumentation) { this._proxyInited = true; this._instrumentNodejs(); } } /** * 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 if in node */ }, { key: "_isValidContext", value: function _isValidContext() { var isNode = typeof process !== 'undefined' && typeof process.release !== 'undefined' && process.release.name === 'node'; return isNode; } }, { key: "_instrumentNodejs", value: function _instrumentNodejs() { var self = this; var tracer = this._tracer; function requestOverride(originalRequest) { // http.request has two overrides, taking url/string first, or options // if url or string morph into an options object, // make it so that options and possible callback are only args passed var options; var callback; var urlObject; /* eslint-disable prefer-destructuring */ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } if (typeof args[0] === 'string' || args[0] instanceof _url.URL) { urlObject = args[0] instanceof _url.URL ? args[0] : new _url.URL(args[0]); options = urlToOptions(urlObject); if (_typeof(args[1]) === 'object') { options = _objectSpread(_objectSpread({}, options), args[1]); callback = args[2]; } else if (typeof args[1] === 'function') { callback = args[1]; } } else { options = args[0]; callback = args[1]; } /* eslint-enable prefer-destructuring */ // check if there are headers stated, and if not create them on the first arg // then grab reference so that we can inject headers into the request before sending the request out if (!options.headers) options.headers = {}; var _options = options, headers = _options.headers; var method = options.method || 'GET'; var url = options.href || _url["default"].format(options); var protocol = options.protocol ? options.protocol.replace(':', '') : url.slice(0, url.indexOf(':')); if (!self._shouldTrace(tracer, url)) { return originalRequest.apply(void 0, args); } var span = tracer.startSpan('node request'); tracer.addActiveRootSpan(span); var tags = { method: method || 'GET', url: url, protocol: protocol }; if (url) { // eslint-disable-next-line prefer-destructuring tags.url_pathname = url.split('?')[0]; } try { var headersCarrier = {}; tracer.inject(span.context(), opentracing.FORMAT_HTTP_HEADERS, headersCarrier); var keys = Object.keys(headersCarrier); // add tracing headers to request // have to set headers instead of modifying the request instance headers, // In an http.get call case, req.end will automatically be called, // setting headers will be impossible after that point // reference https://nodejs.org/api/http.html#http_class_http_clientrequest keys.forEach(function (key) { headers[key] = headersCarrier[key]; }); var request = originalRequest(options, callback); span.log({ event: 'sending', method: method || 'GET', url: url, openPayload: tags }); span.addTags(tags); request.on('response', function (res) { if (res.statusCode >= 500 && res.statusCode <= 599) { span.addTags({ error: true }); } span.log({ method: method || 'GET', headers: res.headers, status: res.status, statusText: res.statusText, responseType: res.type, url: res.url }); span.finish(); tracer.removeActiveRootSpan(span); }); return request; } catch (e) { span.addTags({ error: true }); tracer.removeActiveRootSpan(span); span.log({ event: 'error', error: e }); span.finish(); throw e; } } _http["default"].request = requestOverride.bind(undefined, _http["default"].request); _https["default"].request = requestOverride.bind(undefined, _https["default"].request); _http["default"].get = requestOverride.bind(undefined, _http["default"].get); _https["default"].get = requestOverride.bind(undefined, _https["default"].get); } }, { 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 || !opts.nodejs_instrumentation) { return false; } if (this._internalExclusions.some(function (ex) { return ex.test(url); })) { return false; } var include = false; if (opts.nodejs_url_inclusion_patterns.some(function (inc) { return inc.test(url); })) { include = true; } if (opts.nodejs_url_exclusion_patterns.some(function (ex) { return ex.test(url); })) { include = false; } return include; } }]); return InstrumentNodejs; }(); module.exports = new InstrumentNodejs(); //# sourceMappingURL=instrument_nodejs.js.map