@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
223 lines (184 loc) • 7.44 kB
JavaScript
var {
_optionalChain
} = require('@sentry/utils');
Object.defineProperty(exports, '__esModule', { value: true });
const instrumentationHttp = require('@opentelemetry/instrumentation-http');
const opentelemetry = require('@sentry/opentelemetry');
const core = require('@sentry/core');
const utils = require('@sentry/utils');
const scope = require('../sdk/scope.js');
const addOriginToSpan = require('../utils/addOriginToSpan.js');
const getRequestUrl = require('../utils/getRequestUrl.js');
const INTEGRATION_NAME = 'Http';
let _httpOptions = {};
let _httpInstrumentation;
/**
* Instrument the HTTP module.
* This can only be instrumented once! If this called again later, we just update the options.
*/
const instrumentHttp = Object.assign(
function () {
if (_httpInstrumentation) {
return;
}
const _InstrumentationClass = _httpOptions._instrumentation || instrumentationHttp.HttpInstrumentation;
_httpInstrumentation = new _InstrumentationClass({
..._optionalChain([_httpOptions, 'access', _ => _.instrumentation, 'optionalAccess', _2 => _2._experimentalConfig]),
ignoreOutgoingRequestHook: request => {
const url = getRequestUrl.getRequestUrl(request);
if (!url) {
return false;
}
if (core.isSentryRequestUrl(url, opentelemetry.getClient())) {
return true;
}
const _ignoreOutgoingRequests = _httpOptions.ignoreOutgoingRequests;
if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url, request)) {
return true;
}
return false;
},
ignoreIncomingRequestHook: request => {
// request.url is the only property that holds any information about the url
// it only consists of the URL path and query string (if any)
const urlPath = request.url;
const method = _optionalChain([request, 'access', _3 => _3.method, 'optionalAccess', _4 => _4.toUpperCase, 'call', _5 => _5()]);
// We do not capture OPTIONS/HEAD requests as transactions
if (method === 'OPTIONS' || method === 'HEAD') {
return true;
}
const _ignoreIncomingRequests = _httpOptions.ignoreIncomingRequests;
if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath, request)) {
return true;
}
return false;
},
requireParentforOutgoingSpans: false,
requireParentforIncomingSpans: false,
requestHook: (span, req) => {
addOriginToSpan.addOriginToSpan(span, 'auto.http.otel.http');
// both, incoming requests and "client" requests made within the app trigger the requestHook
// we only want to isolate and further annotate incoming requests (IncomingMessage)
if (_isClientRequest(req)) {
_optionalChain([_httpOptions, 'access', _6 => _6.instrumentation, 'optionalAccess', _7 => _7.requestHook, 'optionalCall', _8 => _8(span, req)]);
return;
}
const scopes = core.getCapturedScopesOnSpan(span);
const isolationScope = (scopes.isolationScope || core.getIsolationScope()).clone();
const scope$1 = scopes.scope || core.getCurrentScope();
// Update the isolation scope, isolate this request
isolationScope.setSDKProcessingMetadata({ request: req });
const client = opentelemetry.getClient();
if (client && client.getOptions().autoSessionTracking) {
isolationScope.setRequestSession({ status: 'ok' });
}
scope.setIsolationScope(isolationScope);
core.setCapturedScopesOnSpan(span, scope$1, isolationScope);
// attempt to update the scope's `transactionName` based on the request URL
// Ideally, framework instrumentations coming after the HttpInstrumentation
// update the transactionName once we get a parameterized route.
const httpMethod = (req.method || 'GET').toUpperCase();
const httpTarget = utils.stripUrlQueryAndFragment(req.url || '/');
const bestEffortTransactionName = `${httpMethod} ${httpTarget}`;
isolationScope.setTransactionName(bestEffortTransactionName);
_optionalChain([_httpOptions, 'access', _9 => _9.instrumentation, 'optionalAccess', _10 => _10.requestHook, 'optionalCall', _11 => _11(span, req)]);
},
responseHook: (span, res) => {
const client = opentelemetry.getClient();
if (client && client.getOptions().autoSessionTracking) {
setImmediate(() => {
client['_captureRequestSession']();
});
}
_optionalChain([_httpOptions, 'access', _12 => _12.instrumentation, 'optionalAccess', _13 => _13.responseHook, 'optionalCall', _14 => _14(span, res)]);
},
applyCustomAttributesOnSpan: (
span,
request,
response,
) => {
const _breadcrumbs = typeof _httpOptions.breadcrumbs === 'undefined' ? true : _httpOptions.breadcrumbs;
if (_breadcrumbs) {
_addRequestBreadcrumb(request, response);
}
_optionalChain([_httpOptions, 'access', _15 => _15.instrumentation, 'optionalAccess', _16 => _16.applyCustomAttributesOnSpan, 'optionalCall', _17 => _17(span, request, response)]);
},
});
opentelemetry.addOpenTelemetryInstrumentation(_httpInstrumentation);
},
{
id: INTEGRATION_NAME,
},
);
const _httpIntegration = ((options = {}) => {
return {
name: INTEGRATION_NAME,
setupOnce() {
_httpOptions = options;
instrumentHttp();
},
};
}) ;
/**
* The http integration instruments Node's internal http and https modules.
* It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span.
*/
const httpIntegration = core.defineIntegration(_httpIntegration);
/** Add a breadcrumb for outgoing requests. */
function _addRequestBreadcrumb(
request,
response,
) {
// Only generate breadcrumbs for outgoing requests
if (!_isClientRequest(request)) {
return;
}
const data = getBreadcrumbData(request);
core.addBreadcrumb(
{
category: 'http',
data: {
status_code: response.statusCode,
...data,
},
type: 'http',
},
{
event: 'response',
request,
response,
},
);
}
function getBreadcrumbData(request) {
try {
// `request.host` does not contain the port, but the host header does
const host = request.getHeader('host') || request.host;
const url = new URL(request.path, `${request.protocol}//${host}`);
const parsedUrl = utils.parseUrl(url.toString());
const data = {
url: utils.getSanitizedUrlString(parsedUrl),
'http.method': request.method || 'GET',
};
if (parsedUrl.search) {
data['http.query'] = parsedUrl.search;
}
if (parsedUrl.hash) {
data['http.fragment'] = parsedUrl.hash;
}
return data;
} catch (e) {
return {};
}
}
/**
* Determines if @param req is a ClientRequest, meaning the request was created within the express app
* and it's an outgoing request.
* Checking for properties instead of using `instanceOf` to avoid importing the request classes.
*/
function _isClientRequest(req) {
return 'outputData' in req && 'outputSize' in req && !('client' in req) && !('statusCode' in req);
}
exports.httpIntegration = httpIntegration;
exports.instrumentHttp = instrumentHttp;
//# sourceMappingURL=http.js.map