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