@sentry/node
Version:
Sentry Node SDK using OpenTelemetry for performance instrumentation
217 lines (180 loc) • 7.38 kB
JavaScript
import { _optionalChain } from '@sentry/utils';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { getClient, addOpenTelemetryInstrumentation } from '@sentry/opentelemetry';
import { isSentryRequestUrl, getCapturedScopesOnSpan, getIsolationScope, getCurrentScope, setCapturedScopesOnSpan, defineIntegration, addBreadcrumb } from '@sentry/core';
import { stripUrlQueryAndFragment, parseUrl, getSanitizedUrlString } from '@sentry/utils';
import { setIsolationScope } from '../sdk/scope.js';
import { addOriginToSpan } from '../utils/addOriginToSpan.js';
import { getRequestUrl } from '../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 || HttpInstrumentation;
_httpInstrumentation = new _InstrumentationClass({
..._optionalChain([_httpOptions, 'access', _ => _.instrumentation, 'optionalAccess', _2 => _2._experimentalConfig]),
ignoreOutgoingRequestHook: request => {
const url = getRequestUrl(request);
if (!url) {
return false;
}
if (isSentryRequestUrl(url, 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(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 = getCapturedScopesOnSpan(span);
const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();
const scope = scopes.scope || getCurrentScope();
// Update the isolation scope, isolate this request
isolationScope.setSDKProcessingMetadata({ request: req });
const client = getClient();
if (client && client.getOptions().autoSessionTracking) {
isolationScope.setRequestSession({ status: 'ok' });
}
setIsolationScope(isolationScope);
setCapturedScopesOnSpan(span, scope, 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 = 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 = 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)]);
},
});
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 = 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);
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 = parseUrl(url.toString());
const data = {
url: 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);
}
export { httpIntegration, instrumentHttp };
//# sourceMappingURL=http.js.map