UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

223 lines (184 loc) • 7.44 kB
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