123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- import { _optionalChain } from '@sentry/utils';
- import { defineIntegration, getClient, isSentryRequestUrl, getCurrentScope, getIsolationScope, getActiveSpan, spanToTraceHeader, getDynamicSamplingContextFromSpan, getDynamicSamplingContextFromClient, setHttpStatus, spanToJSON, hasTracingEnabled, getCurrentHub, addBreadcrumb } from '@sentry/core';
- import { dropUndefinedKeys, logger, fill, LRUMap, generateSentryTraceHeader, dynamicSamplingContextToSentryBaggageHeader, stringMatchesSomePattern } from '@sentry/utils';
- import { DEBUG_BUILD } from '../debug-build.js';
- import { NODE_VERSION } from '../nodeVersion.js';
- import { normalizeRequestArgs, extractRawUrl, extractUrl, cleanSpanDescription } from './utils/http.js';
- const _httpIntegration = ((options = {}) => {
- const { breadcrumbs, tracing, shouldCreateSpanForRequest } = options;
- const convertedOptions = {
- breadcrumbs,
- tracing:
- tracing === false
- ? false
- : 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 = 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,
- 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') {
- DEBUG_BUILD && 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,
- );
- fill(httpModule, 'get', wrappedHttpHandlerMaker);
- 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 (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,
- );
- fill(httpsModule, 'get', wrappedHttpsHandlerMaker);
- 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 LRUMap(100);
- const headersUrlMap = new 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 = 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 (!getCurrentHub().getIntegration(Http)) {
- return;
- }
- 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 = normalizeRequestArgs(httpModule, args);
- const requestOptions = requestArgs[0];
- // eslint-disable-next-line deprecation/deprecation
- const rawRequestUrl = extractRawUrl(requestOptions);
- const requestUrl = extractUrl(requestOptions);
- const client = getClient();
- // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method
- if (isSentryRequestUrl(requestUrl, client)) {
- return originalRequestMethod.apply(httpModule, requestArgs);
- }
- const scope = getCurrentScope();
- const isolationScope = getIsolationScope();
- const parentSpan = 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
- ? spanToTraceHeader(requestSpan)
- : generateSentryTraceHeader(traceId, spanId, sampled);
- const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
- dsc ||
- (requestSpan
- ? getDynamicSamplingContextFromSpan(requestSpan)
- : getDynamicSamplingContextFromClient(traceId, client, scope)),
- );
- addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader);
- } else {
- DEBUG_BUILD &&
- 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) {
- setHttpStatus(requestSpan, res.statusCode);
- }
- requestSpan.updateName(
- cleanSpanDescription(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) {
- setHttpStatus(requestSpan, 500);
- requestSpan.updateName(
- cleanSpanDescription(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;
- }
- DEBUG_BUILD &&
- 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
- ? 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;
- }
- export { Http, _getShouldCreateSpanForRequest, _shouldCreateSpans, httpIntegration };
- //# sourceMappingURL=http.js.map
|