http.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. var {
  2. _optionalChain
  3. } = require('@sentry/utils');
  4. Object.defineProperty(exports, '__esModule', { value: true });
  5. const core = require('@sentry/core');
  6. const utils = require('@sentry/utils');
  7. const debugBuild = require('../debug-build.js');
  8. const nodeVersion = require('../nodeVersion.js');
  9. const http = require('./utils/http.js');
  10. const _httpIntegration = ((options = {}) => {
  11. const { breadcrumbs, tracing, shouldCreateSpanForRequest } = options;
  12. const convertedOptions = {
  13. breadcrumbs,
  14. tracing:
  15. tracing === false
  16. ? false
  17. : utils.dropUndefinedKeys({
  18. // If tracing is forced to `true`, we don't want to set `enableIfHasTracingEnabled`
  19. enableIfHasTracingEnabled: tracing === true ? undefined : true,
  20. shouldCreateSpanForRequest,
  21. }),
  22. };
  23. // eslint-disable-next-line deprecation/deprecation
  24. return new Http(convertedOptions) ;
  25. }) ;
  26. /**
  27. * The http module integration instruments Node's internal http module. It creates breadcrumbs, spans for outgoing
  28. * http requests, and attaches trace data when tracing is enabled via its `tracing` option.
  29. *
  30. * By default, this will always create breadcrumbs, and will create spans if tracing is enabled.
  31. */
  32. const httpIntegration = core.defineIntegration(_httpIntegration);
  33. /**
  34. * The http module integration instruments Node's internal http module. It creates breadcrumbs, transactions for outgoing
  35. * http requests and attaches trace data when tracing is enabled via its `tracing` option.
  36. *
  37. * @deprecated Use `httpIntegration()` instead.
  38. */
  39. class Http {
  40. /**
  41. * @inheritDoc
  42. */
  43. static __initStatic() {this.id = 'Http';}
  44. /**
  45. * @inheritDoc
  46. */
  47. // eslint-disable-next-line deprecation/deprecation
  48. __init() {this.name = Http.id;}
  49. /**
  50. * @inheritDoc
  51. */
  52. constructor(options = {}) {Http.prototype.__init.call(this);
  53. this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
  54. this._tracing = !options.tracing ? undefined : options.tracing === true ? {} : options.tracing;
  55. }
  56. /**
  57. * @inheritDoc
  58. */
  59. setupOnce(
  60. _addGlobalEventProcessor,
  61. setupOnceGetCurrentHub,
  62. ) {
  63. // eslint-disable-next-line deprecation/deprecation
  64. const clientOptions = _optionalChain([setupOnceGetCurrentHub, 'call', _ => _(), 'access', _2 => _2.getClient, 'call', _3 => _3(), 'optionalAccess', _4 => _4.getOptions, 'call', _5 => _5()]);
  65. // If `tracing` is not explicitly set, we default this based on whether or not tracing is enabled.
  66. // But for compatibility, we only do that if `enableIfHasTracingEnabled` is set.
  67. const shouldCreateSpans = _shouldCreateSpans(this._tracing, clientOptions);
  68. // No need to instrument if we don't want to track anything
  69. if (!this._breadcrumbs && !shouldCreateSpans) {
  70. return;
  71. }
  72. // Do not auto-instrument for other instrumenter
  73. if (clientOptions && clientOptions.instrumenter !== 'sentry') {
  74. debugBuild.DEBUG_BUILD && utils.logger.log('HTTP Integration is skipped because of instrumenter configuration.');
  75. return;
  76. }
  77. const shouldCreateSpanForRequest = _getShouldCreateSpanForRequest(shouldCreateSpans, this._tracing, clientOptions);
  78. // eslint-disable-next-line deprecation/deprecation
  79. const tracePropagationTargets = _optionalChain([clientOptions, 'optionalAccess', _6 => _6.tracePropagationTargets]) || _optionalChain([this, 'access', _7 => _7._tracing, 'optionalAccess', _8 => _8.tracePropagationTargets]);
  80. // eslint-disable-next-line @typescript-eslint/no-var-requires
  81. const httpModule = require('http');
  82. const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory(
  83. httpModule,
  84. this._breadcrumbs,
  85. shouldCreateSpanForRequest,
  86. tracePropagationTargets,
  87. );
  88. utils.fill(httpModule, 'get', wrappedHttpHandlerMaker);
  89. utils.fill(httpModule, 'request', wrappedHttpHandlerMaker);
  90. // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it.
  91. // If we do, we'd get double breadcrumbs and double spans for `https` calls.
  92. // It has been changed in Node 9, so for all versions equal and above, we patch `https` separately.
  93. if (nodeVersion.NODE_VERSION.major > 8) {
  94. // eslint-disable-next-line @typescript-eslint/no-var-requires
  95. const httpsModule = require('https');
  96. const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory(
  97. httpsModule,
  98. this._breadcrumbs,
  99. shouldCreateSpanForRequest,
  100. tracePropagationTargets,
  101. );
  102. utils.fill(httpsModule, 'get', wrappedHttpsHandlerMaker);
  103. utils.fill(httpsModule, 'request', wrappedHttpsHandlerMaker);
  104. }
  105. }
  106. }Http.__initStatic();
  107. // for ease of reading below
  108. /**
  109. * Function which creates a function which creates wrapped versions of internal `request` and `get` calls within `http`
  110. * and `https` modules. (NB: Not a typo - this is a creator^2!)
  111. *
  112. * @param breadcrumbsEnabled Whether or not to record outgoing requests as breadcrumbs
  113. * @param tracingEnabled Whether or not to record outgoing requests as tracing spans
  114. *
  115. * @returns A function which accepts the exiting handler and returns a wrapped handler
  116. */
  117. function _createWrappedRequestMethodFactory(
  118. httpModule,
  119. breadcrumbsEnabled,
  120. shouldCreateSpanForRequest,
  121. tracePropagationTargets,
  122. ) {
  123. // We're caching results so we don't have to recompute regexp every time we create a request.
  124. const createSpanUrlMap = new utils.LRUMap(100);
  125. const headersUrlMap = new utils.LRUMap(100);
  126. const shouldCreateSpan = (url) => {
  127. if (shouldCreateSpanForRequest === undefined) {
  128. return true;
  129. }
  130. const cachedDecision = createSpanUrlMap.get(url);
  131. if (cachedDecision !== undefined) {
  132. return cachedDecision;
  133. }
  134. const decision = shouldCreateSpanForRequest(url);
  135. createSpanUrlMap.set(url, decision);
  136. return decision;
  137. };
  138. const shouldAttachTraceData = (url) => {
  139. if (tracePropagationTargets === undefined) {
  140. return true;
  141. }
  142. const cachedDecision = headersUrlMap.get(url);
  143. if (cachedDecision !== undefined) {
  144. return cachedDecision;
  145. }
  146. const decision = utils.stringMatchesSomePattern(url, tracePropagationTargets);
  147. headersUrlMap.set(url, decision);
  148. return decision;
  149. };
  150. /**
  151. * Captures Breadcrumb based on provided request/response pair
  152. */
  153. function addRequestBreadcrumb(
  154. event,
  155. requestSpanData,
  156. req,
  157. res,
  158. ) {
  159. // eslint-disable-next-line deprecation/deprecation
  160. if (!core.getCurrentHub().getIntegration(Http)) {
  161. return;
  162. }
  163. core.addBreadcrumb(
  164. {
  165. category: 'http',
  166. data: {
  167. status_code: res && res.statusCode,
  168. ...requestSpanData,
  169. },
  170. type: 'http',
  171. },
  172. {
  173. event,
  174. request: req,
  175. response: res,
  176. },
  177. );
  178. }
  179. return function wrappedRequestMethodFactory(originalRequestMethod) {
  180. return function wrappedMethod( ...args) {
  181. const requestArgs = http.normalizeRequestArgs(httpModule, args);
  182. const requestOptions = requestArgs[0];
  183. // eslint-disable-next-line deprecation/deprecation
  184. const rawRequestUrl = http.extractRawUrl(requestOptions);
  185. const requestUrl = http.extractUrl(requestOptions);
  186. const client = core.getClient();
  187. // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method
  188. if (core.isSentryRequestUrl(requestUrl, client)) {
  189. return originalRequestMethod.apply(httpModule, requestArgs);
  190. }
  191. const scope = core.getCurrentScope();
  192. const isolationScope = core.getIsolationScope();
  193. const parentSpan = core.getActiveSpan();
  194. const data = getRequestSpanData(requestUrl, requestOptions);
  195. const requestSpan = shouldCreateSpan(rawRequestUrl)
  196. ? // eslint-disable-next-line deprecation/deprecation
  197. _optionalChain([parentSpan, 'optionalAccess', _9 => _9.startChild, 'call', _10 => _10({
  198. op: 'http.client',
  199. origin: 'auto.http.node.http',
  200. description: `${data['http.method']} ${data.url}`,
  201. data,
  202. })])
  203. : undefined;
  204. if (client && shouldAttachTraceData(rawRequestUrl)) {
  205. const { traceId, spanId, sampled, dsc } = {
  206. ...isolationScope.getPropagationContext(),
  207. ...scope.getPropagationContext(),
  208. };
  209. const sentryTraceHeader = requestSpan
  210. ? core.spanToTraceHeader(requestSpan)
  211. : utils.generateSentryTraceHeader(traceId, spanId, sampled);
  212. const sentryBaggageHeader = utils.dynamicSamplingContextToSentryBaggageHeader(
  213. dsc ||
  214. (requestSpan
  215. ? core.getDynamicSamplingContextFromSpan(requestSpan)
  216. : core.getDynamicSamplingContextFromClient(traceId, client, scope)),
  217. );
  218. addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader);
  219. } else {
  220. debugBuild.DEBUG_BUILD &&
  221. utils.logger.log(
  222. `[Tracing] Not adding sentry-trace header to outgoing request (${requestUrl}) due to mismatching tracePropagationTargets option.`,
  223. );
  224. }
  225. // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
  226. return originalRequestMethod
  227. .apply(httpModule, requestArgs)
  228. .once('response', function ( res) {
  229. // eslint-disable-next-line @typescript-eslint/no-this-alias
  230. const req = this;
  231. if (breadcrumbsEnabled) {
  232. addRequestBreadcrumb('response', data, req, res);
  233. }
  234. if (requestSpan) {
  235. if (res.statusCode) {
  236. core.setHttpStatus(requestSpan, res.statusCode);
  237. }
  238. requestSpan.updateName(
  239. http.cleanSpanDescription(core.spanToJSON(requestSpan).description || '', requestOptions, req) || '',
  240. );
  241. requestSpan.end();
  242. }
  243. })
  244. .once('error', function () {
  245. // eslint-disable-next-line @typescript-eslint/no-this-alias
  246. const req = this;
  247. if (breadcrumbsEnabled) {
  248. addRequestBreadcrumb('error', data, req);
  249. }
  250. if (requestSpan) {
  251. core.setHttpStatus(requestSpan, 500);
  252. requestSpan.updateName(
  253. http.cleanSpanDescription(core.spanToJSON(requestSpan).description || '', requestOptions, req) || '',
  254. );
  255. requestSpan.end();
  256. }
  257. });
  258. };
  259. };
  260. }
  261. function addHeadersToRequestOptions(
  262. requestOptions,
  263. requestUrl,
  264. sentryTraceHeader,
  265. sentryBaggageHeader,
  266. ) {
  267. // Don't overwrite sentry-trace and baggage header if it's already set.
  268. const headers = requestOptions.headers || {};
  269. if (headers['sentry-trace']) {
  270. return;
  271. }
  272. debugBuild.DEBUG_BUILD &&
  273. utils.logger.log(`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `);
  274. requestOptions.headers = {
  275. ...requestOptions.headers,
  276. 'sentry-trace': sentryTraceHeader,
  277. // Setting a header to `undefined` will crash in node so we only set the baggage header when it's defined
  278. ...(sentryBaggageHeader &&
  279. sentryBaggageHeader.length > 0 && { baggage: normalizeBaggageHeader(requestOptions, sentryBaggageHeader) }),
  280. };
  281. }
  282. function getRequestSpanData(requestUrl, requestOptions) {
  283. const method = requestOptions.method || 'GET';
  284. const data = {
  285. url: requestUrl,
  286. 'http.method': method,
  287. };
  288. if (requestOptions.hash) {
  289. // strip leading "#"
  290. data['http.fragment'] = requestOptions.hash.substring(1);
  291. }
  292. if (requestOptions.search) {
  293. // strip leading "?"
  294. data['http.query'] = requestOptions.search.substring(1);
  295. }
  296. return data;
  297. }
  298. function normalizeBaggageHeader(
  299. requestOptions,
  300. sentryBaggageHeader,
  301. ) {
  302. if (!requestOptions.headers || !requestOptions.headers.baggage) {
  303. return sentryBaggageHeader;
  304. } else if (!sentryBaggageHeader) {
  305. return requestOptions.headers.baggage ;
  306. } else if (Array.isArray(requestOptions.headers.baggage)) {
  307. return [...requestOptions.headers.baggage, sentryBaggageHeader];
  308. }
  309. // Type-cast explanation:
  310. // Technically this the following could be of type `(number | string)[]` but for the sake of simplicity
  311. // we say this is undefined behaviour, since it would not be baggage spec conform if the user did this.
  312. return [requestOptions.headers.baggage, sentryBaggageHeader] ;
  313. }
  314. /** Exported for tests only. */
  315. function _shouldCreateSpans(
  316. tracingOptions,
  317. clientOptions,
  318. ) {
  319. return tracingOptions === undefined
  320. ? false
  321. : tracingOptions.enableIfHasTracingEnabled
  322. ? core.hasTracingEnabled(clientOptions)
  323. : true;
  324. }
  325. /** Exported for tests only. */
  326. function _getShouldCreateSpanForRequest(
  327. shouldCreateSpans,
  328. tracingOptions,
  329. clientOptions,
  330. ) {
  331. const handler = shouldCreateSpans
  332. ? // eslint-disable-next-line deprecation/deprecation
  333. _optionalChain([tracingOptions, 'optionalAccess', _11 => _11.shouldCreateSpanForRequest]) || _optionalChain([clientOptions, 'optionalAccess', _12 => _12.shouldCreateSpanForRequest])
  334. : () => false;
  335. return handler;
  336. }
  337. exports.Http = Http;
  338. exports._getShouldCreateSpanForRequest = _getShouldCreateSpanForRequest;
  339. exports._shouldCreateSpans = _shouldCreateSpans;
  340. exports.httpIntegration = httpIntegration;
  341. //# sourceMappingURL=http.js.map