UNPKG

@sentry/node

Version:
316 lines (280 loc) 11.5 kB
var { _optionalChain } = require('@sentry/utils/cjs/buildPolyfills'); Object.defineProperty(exports, '__esModule', { value: true }); const core = require('@sentry/core'); const utils = require('@sentry/utils'); const lru_map = require('lru_map'); const nodeVersion = require('../nodeVersion.js'); const http = require('./utils/http.js'); /** * The http module integration instruments Node's internal http module. It creates breadcrumbs, transactions for outgoing * http requests and attaches trace data when tracing is enabled via its `tracing` option. */ class Http { /** * @inheritDoc */ static __initStatic() {this.id = 'Http';} /** * @inheritDoc */ __init() {this.name = Http.id;} /** * @inheritDoc */ constructor(options = {}) {Http.prototype.__init.call(this); this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs; this._tracing = !options.tracing ? undefined : options.tracing === true ? {} : options.tracing; } /** * @inheritDoc */ setupOnce( _addGlobalEventProcessor, setupOnceGetCurrentHub, ) { // No need to instrument if we don't want to track anything if (!this._breadcrumbs && !this._tracing) { return; } const clientOptions = _optionalChain([setupOnceGetCurrentHub, 'call', _ => _(), 'access', _2 => _2.getClient, 'call', _3 => _3(), 'optionalAccess', _4 => _4.getOptions, 'call', _5 => _5()]); // Do not auto-instrument for other instrumenter if (clientOptions && clientOptions.instrumenter !== 'sentry') { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('HTTP Integration is skipped because of instrumenter configuration.'); return; } const shouldCreateSpanForRequest = // eslint-disable-next-line deprecation/deprecation _optionalChain([this, 'access', _6 => _6._tracing, 'optionalAccess', _7 => _7.shouldCreateSpanForRequest]) || _optionalChain([clientOptions, 'optionalAccess', _8 => _8.shouldCreateSpanForRequest]); // eslint-disable-next-line deprecation/deprecation const tracePropagationTargets = _optionalChain([clientOptions, 'optionalAccess', _9 => _9.tracePropagationTargets]) || _optionalChain([this, 'access', _10 => _10._tracing, 'optionalAccess', _11 => _11.tracePropagationTargets]); // eslint-disable-next-line @typescript-eslint/no-var-requires const httpModule = require('http'); const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory( httpModule, this._breadcrumbs, shouldCreateSpanForRequest, tracePropagationTargets, ); utils.fill(httpModule, 'get', wrappedHttpHandlerMaker); utils.fill(httpModule, 'request', wrappedHttpHandlerMaker); // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it. // If we do, we'd get double breadcrumbs and double spans for `https` calls. // It has been changed in Node 9, so for all versions equal and above, we patch `https` separately. if (nodeVersion.NODE_VERSION.major && nodeVersion.NODE_VERSION.major > 8) { // eslint-disable-next-line @typescript-eslint/no-var-requires const httpsModule = require('https'); const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory( httpsModule, this._breadcrumbs, shouldCreateSpanForRequest, tracePropagationTargets, ); utils.fill(httpsModule, 'get', wrappedHttpsHandlerMaker); utils.fill(httpsModule, 'request', wrappedHttpsHandlerMaker); } } }Http.__initStatic(); // for ease of reading below /** * Function which creates a function which creates wrapped versions of internal `request` and `get` calls within `http` * and `https` modules. (NB: Not a typo - this is a creator^2!) * * @param breadcrumbsEnabled Whether or not to record outgoing requests as breadcrumbs * @param tracingEnabled Whether or not to record outgoing requests as tracing spans * * @returns A function which accepts the exiting handler and returns a wrapped handler */ function _createWrappedRequestMethodFactory( httpModule, breadcrumbsEnabled, shouldCreateSpanForRequest, tracePropagationTargets, ) { // We're caching results so we don't have to recompute regexp every time we create a request. const createSpanUrlMap = new lru_map.LRUMap(100); const headersUrlMap = new lru_map.LRUMap(100); const shouldCreateSpan = (url) => { if (shouldCreateSpanForRequest === undefined) { return true; } const cachedDecision = createSpanUrlMap.get(url); if (cachedDecision !== undefined) { return cachedDecision; } const decision = shouldCreateSpanForRequest(url); createSpanUrlMap.set(url, decision); return decision; }; const shouldAttachTraceData = (url) => { if (tracePropagationTargets === undefined) { return true; } const cachedDecision = headersUrlMap.get(url); if (cachedDecision !== undefined) { return cachedDecision; } const decision = utils.stringMatchesSomePattern(url, tracePropagationTargets); headersUrlMap.set(url, decision); return decision; }; /** * Captures Breadcrumb based on provided request/response pair */ function addRequestBreadcrumb( event, requestSpanData, req, res, ) { if (!core.getCurrentHub().getIntegration(Http)) { return; } core.getCurrentHub().addBreadcrumb( { category: 'http', data: { status_code: res && res.statusCode, ...requestSpanData, }, type: 'http', }, { event, request: req, response: res, }, ); } return function wrappedRequestMethodFactory(originalRequestMethod) { return function wrappedMethod( ...args) { const requestArgs = http.normalizeRequestArgs(httpModule, args); const requestOptions = requestArgs[0]; // eslint-disable-next-line deprecation/deprecation const rawRequestUrl = http.extractRawUrl(requestOptions); const requestUrl = http.extractUrl(requestOptions); // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method if (http.isSentryRequest(requestUrl)) { return originalRequestMethod.apply(httpModule, requestArgs); } const hub = core.getCurrentHub(); const scope = hub.getScope(); const parentSpan = scope.getSpan(); const data = getRequestSpanData(requestUrl, requestOptions); const requestSpan = shouldCreateSpan(rawRequestUrl) ? _optionalChain([parentSpan, 'optionalAccess', _12 => _12.startChild, 'call', _13 => _13({ op: 'http.client', origin: 'auto.http.node.http', description: `${data['http.method']} ${data.url}`, data, })]) : undefined; if (shouldAttachTraceData(rawRequestUrl)) { if (requestSpan) { const sentryTraceHeader = requestSpan.toTraceparent(); const dynamicSamplingContext = _optionalChain([requestSpan, 'optionalAccess', _14 => _14.transaction, 'optionalAccess', _15 => _15.getDynamicSamplingContext, 'call', _16 => _16()]); addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, dynamicSamplingContext); } else { const client = hub.getClient(); const { traceId, sampled, dsc } = scope.getPropagationContext(); const sentryTraceHeader = utils.generateSentryTraceHeader(traceId, undefined, sampled); const dynamicSamplingContext = dsc || (client ? core.getDynamicSamplingContextFromClient(traceId, client, scope) : undefined); addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, dynamicSamplingContext); } } else { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log( `[Tracing] Not adding sentry-trace header to outgoing request (${requestUrl}) due to mismatching tracePropagationTargets option.`, ); } // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return originalRequestMethod .apply(httpModule, requestArgs) .once('response', function ( res) { // eslint-disable-next-line @typescript-eslint/no-this-alias const req = this; if (breadcrumbsEnabled) { addRequestBreadcrumb('response', data, req, res); } if (requestSpan) { if (res.statusCode) { requestSpan.setHttpStatus(res.statusCode); } requestSpan.description = http.cleanSpanDescription(requestSpan.description, requestOptions, req); requestSpan.finish(); } }) .once('error', function () { // eslint-disable-next-line @typescript-eslint/no-this-alias const req = this; if (breadcrumbsEnabled) { addRequestBreadcrumb('error', data, req); } if (requestSpan) { requestSpan.setHttpStatus(500); requestSpan.description = http.cleanSpanDescription(requestSpan.description, requestOptions, req); requestSpan.finish(); } }); }; }; } function addHeadersToRequestOptions( requestOptions, requestUrl, sentryTraceHeader, dynamicSamplingContext, ) { // Don't overwrite sentry-trace and baggage header if it's already set. const headers = requestOptions.headers || {}; if (headers['sentry-trace']) { return; } (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `); const sentryBaggage = utils.dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); const sentryBaggageHeader = sentryBaggage && sentryBaggage.length > 0 ? normalizeBaggageHeader(requestOptions, sentryBaggage) : undefined; requestOptions.headers = { ...requestOptions.headers, 'sentry-trace': sentryTraceHeader, // Setting a header to `undefined` will crash in node so we only set the baggage header when it's defined ...(sentryBaggageHeader && { baggage: sentryBaggageHeader }), }; } function getRequestSpanData(requestUrl, requestOptions) { const method = requestOptions.method || 'GET'; const data = { url: requestUrl, 'http.method': method, }; if (requestOptions.hash) { // strip leading "#" data['http.fragment'] = requestOptions.hash.substring(1); } if (requestOptions.search) { // strip leading "?" data['http.query'] = requestOptions.search.substring(1); } return data; } function normalizeBaggageHeader( requestOptions, sentryBaggageHeader, ) { if (!requestOptions.headers || !requestOptions.headers.baggage) { return sentryBaggageHeader; } else if (!sentryBaggageHeader) { return requestOptions.headers.baggage ; } else if (Array.isArray(requestOptions.headers.baggage)) { return [...requestOptions.headers.baggage, sentryBaggageHeader]; } // Type-cast explanation: // Technically this the following could be of type `(number | string)[]` but for the sake of simplicity // we say this is undefined behaviour, since it would not be baggage spec conform if the user did this. return [requestOptions.headers.baggage, sentryBaggageHeader] ; } exports.Http = Http; //# sourceMappingURL=http.js.map