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