var { _optionalChain } = require('@sentry/utils'); Object.defineProperty(exports, '__esModule', { value: true }); const core = require('@sentry/core'); const utils = require('@sentry/utils'); const debugBuild = require('../debug-build.js'); const nodeVersion = require('../nodeVersion.js'); const http = require('./utils/http.js'); const _httpIntegration = ((options = {}) => { const { breadcrumbs, tracing, shouldCreateSpanForRequest } = options; const convertedOptions = { breadcrumbs, tracing: tracing === false ? false : utils.dropUndefinedKeys({ // If tracing is forced to `true`, we don't want to set `enableIfHasTracingEnabled` enableIfHasTracingEnabled: tracing === true ? undefined : true, shouldCreateSpanForRequest, }), }; // eslint-disable-next-line deprecation/deprecation return new Http(convertedOptions) ; }) ; /** * The http module integration instruments Node's internal http module. It creates breadcrumbs, spans for outgoing * http requests, and attaches trace data when tracing is enabled via its `tracing` option. * * By default, this will always create breadcrumbs, and will create spans if tracing is enabled. */ const httpIntegration = core.defineIntegration(_httpIntegration); /** * 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. * * @deprecated Use `httpIntegration()` instead. */ class Http { /** * @inheritDoc */ static __initStatic() {this.id = 'Http';} /** * @inheritDoc */ // eslint-disable-next-line deprecation/deprecation __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, // eslint-disable-next-line deprecation/deprecation setupOnceGetCurrentHub, ) { // eslint-disable-next-line deprecation/deprecation const clientOptions = _optionalChain([setupOnceGetCurrentHub, 'call', _ => _(), 'access', _2 => _2.getClient, 'call', _3 => _3(), 'optionalAccess', _4 => _4.getOptions, 'call', _5 => _5()]); // If `tracing` is not explicitly set, we default this based on whether or not tracing is enabled. // But for compatibility, we only do that if `enableIfHasTracingEnabled` is set. const shouldCreateSpans = _shouldCreateSpans(this._tracing, clientOptions); // No need to instrument if we don't want to track anything if (!this._breadcrumbs && !shouldCreateSpans) { return; } // Do not auto-instrument for other instrumenter if (clientOptions && clientOptions.instrumenter !== 'sentry') { debugBuild.DEBUG_BUILD && utils.logger.log('HTTP Integration is skipped because of instrumenter configuration.'); return; } const shouldCreateSpanForRequest = _getShouldCreateSpanForRequest(shouldCreateSpans, this._tracing, clientOptions); // eslint-disable-next-line deprecation/deprecation const tracePropagationTargets = _optionalChain([clientOptions, 'optionalAccess', _6 => _6.tracePropagationTargets]) || _optionalChain([this, 'access', _7 => _7._tracing, 'optionalAccess', _8 => _8.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 > 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 utils.LRUMap(100); const headersUrlMap = new utils.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, ) { // eslint-disable-next-line deprecation/deprecation if (!core.getCurrentHub().getIntegration(Http)) { return; } core.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); const client = core.getClient(); // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method if (core.isSentryRequestUrl(requestUrl, client)) { return originalRequestMethod.apply(httpModule, requestArgs); } const scope = core.getCurrentScope(); const isolationScope = core.getIsolationScope(); const parentSpan = core.getActiveSpan(); const data = getRequestSpanData(requestUrl, requestOptions); const requestSpan = shouldCreateSpan(rawRequestUrl) ? // eslint-disable-next-line deprecation/deprecation _optionalChain([parentSpan, 'optionalAccess', _9 => _9.startChild, 'call', _10 => _10({ op: 'http.client', origin: 'auto.http.node.http', description: `${data['http.method']} ${data.url}`, data, })]) : undefined; if (client && shouldAttachTraceData(rawRequestUrl)) { const { traceId, spanId, sampled, dsc } = { ...isolationScope.getPropagationContext(), ...scope.getPropagationContext(), }; const sentryTraceHeader = requestSpan ? core.spanToTraceHeader(requestSpan) : utils.generateSentryTraceHeader(traceId, spanId, sampled); const sentryBaggageHeader = utils.dynamicSamplingContextToSentryBaggageHeader( dsc || (requestSpan ? core.getDynamicSamplingContextFromSpan(requestSpan) : core.getDynamicSamplingContextFromClient(traceId, client, scope)), ); addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader); } else { debugBuild.DEBUG_BUILD && 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) { core.setHttpStatus(requestSpan, res.statusCode); } requestSpan.updateName( http.cleanSpanDescription(core.spanToJSON(requestSpan).description || '', requestOptions, req) || '', ); requestSpan.end(); } }) .once('error', function () { // eslint-disable-next-line @typescript-eslint/no-this-alias const req = this; if (breadcrumbsEnabled) { addRequestBreadcrumb('error', data, req); } if (requestSpan) { core.setHttpStatus(requestSpan, 500); requestSpan.updateName( http.cleanSpanDescription(core.spanToJSON(requestSpan).description || '', requestOptions, req) || '', ); requestSpan.end(); } }); }; }; } function addHeadersToRequestOptions( requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader, ) { // Don't overwrite sentry-trace and baggage header if it's already set. const headers = requestOptions.headers || {}; if (headers['sentry-trace']) { return; } debugBuild.DEBUG_BUILD && utils.logger.log(`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `); 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 && sentryBaggageHeader.length > 0 && { baggage: normalizeBaggageHeader(requestOptions, 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] ; } /** Exported for tests only. */ function _shouldCreateSpans( tracingOptions, clientOptions, ) { return tracingOptions === undefined ? false : tracingOptions.enableIfHasTracingEnabled ? core.hasTracingEnabled(clientOptions) : true; } /** Exported for tests only. */ function _getShouldCreateSpanForRequest( shouldCreateSpans, tracingOptions, clientOptions, ) { const handler = shouldCreateSpans ? // eslint-disable-next-line deprecation/deprecation _optionalChain([tracingOptions, 'optionalAccess', _11 => _11.shouldCreateSpanForRequest]) || _optionalChain([clientOptions, 'optionalAccess', _12 => _12.shouldCreateSpanForRequest]) : () => false; return handler; } exports.Http = Http; exports._getShouldCreateSpanForRequest = _getShouldCreateSpanForRequest; exports._shouldCreateSpans = _shouldCreateSpans; exports.httpIntegration = httpIntegration; //# sourceMappingURL=http.js.map