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