@sentry/node
Version:
Official Sentry SDK for Node.js
316 lines (280 loc) • 11.5 kB
JavaScript
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