index.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import { _optionalChain } from '@sentry/utils';
  2. import { defineIntegration, hasTracingEnabled, getClient, isSentryRequestUrl, getCurrentScope, getIsolationScope, getActiveSpan, spanToTraceHeader, getDynamicSamplingContextFromSpan, getDynamicSamplingContextFromClient, setHttpStatus, addBreadcrumb } from '@sentry/core';
  3. import { LRUMap, generateSentryTraceHeader, dynamicSamplingContextToSentryBaggageHeader, parseUrl, stringMatchesSomePattern, getSanitizedUrlString } from '@sentry/utils';
  4. import { NODE_VERSION } from '../../nodeVersion.js';
  5. var ChannelName;(function (ChannelName) {
  6. // https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md#undicirequestcreate
  7. const RequestCreate = 'undici:request:create'; ChannelName["RequestCreate"] = RequestCreate;
  8. const RequestEnd = 'undici:request:headers'; ChannelName["RequestEnd"] = RequestEnd;
  9. const RequestError = 'undici:request:error'; ChannelName["RequestError"] = RequestError;
  10. })(ChannelName || (ChannelName = {}));
  11. // Please note that you cannot use `console.log` to debug the callbacks registered to the `diagnostics_channel` API.
  12. // To debug, you can use `writeFileSync` to write to a file:
  13. // https://nodejs.org/api/async_hooks.html#printing-in-asynchook-callbacks
  14. //
  15. // import { writeFileSync } from 'fs';
  16. // import { format } from 'util';
  17. //
  18. // function debug(...args: any): void {
  19. // // Use a function like this one when debugging inside an AsyncHook callback
  20. // // @ts-expect-error any
  21. // writeFileSync('log.out', `${format(...args)}\n`, { flag: 'a' });
  22. // }
  23. const _nativeNodeFetchintegration = ((options) => {
  24. // eslint-disable-next-line deprecation/deprecation
  25. return new Undici(options) ;
  26. }) ;
  27. const nativeNodeFetchintegration = defineIntegration(_nativeNodeFetchintegration);
  28. /**
  29. * Instruments outgoing HTTP requests made with the `undici` package via
  30. * Node's `diagnostics_channel` API.
  31. *
  32. * Supports Undici 4.7.0 or higher.
  33. *
  34. * Requires Node 16.17.0 or higher.
  35. *
  36. * @deprecated Use `nativeNodeFetchintegration()` instead.
  37. */
  38. class Undici {
  39. /**
  40. * @inheritDoc
  41. */
  42. static __initStatic() {this.id = 'Undici';}
  43. /**
  44. * @inheritDoc
  45. */
  46. // eslint-disable-next-line deprecation/deprecation
  47. __init() {this.name = Undici.id;}
  48. __init2() {this._createSpanUrlMap = new LRUMap(100);}
  49. __init3() {this._headersUrlMap = new LRUMap(100);}
  50. constructor(_options = {}) {Undici.prototype.__init.call(this);Undici.prototype.__init2.call(this);Undici.prototype.__init3.call(this);Undici.prototype.__init4.call(this);Undici.prototype.__init5.call(this);Undici.prototype.__init6.call(this);
  51. this._options = {
  52. breadcrumbs: _options.breadcrumbs === undefined ? true : _options.breadcrumbs,
  53. tracing: _options.tracing,
  54. shouldCreateSpanForRequest: _options.shouldCreateSpanForRequest,
  55. };
  56. }
  57. /**
  58. * @inheritDoc
  59. */
  60. setupOnce(_addGlobalEventProcessor) {
  61. // Requires Node 16+ to use the diagnostics_channel API.
  62. if (NODE_VERSION.major < 16) {
  63. return;
  64. }
  65. let ds;
  66. try {
  67. // eslint-disable-next-line @typescript-eslint/no-var-requires
  68. ds = require('diagnostics_channel') ;
  69. } catch (e) {
  70. // no-op
  71. }
  72. if (!ds || !ds.subscribe) {
  73. return;
  74. }
  75. // https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md
  76. ds.subscribe(ChannelName.RequestCreate, this._onRequestCreate);
  77. ds.subscribe(ChannelName.RequestEnd, this._onRequestEnd);
  78. ds.subscribe(ChannelName.RequestError, this._onRequestError);
  79. }
  80. /** Helper that wraps shouldCreateSpanForRequest option */
  81. _shouldCreateSpan(url) {
  82. if (this._options.tracing === false || (this._options.tracing === undefined && !hasTracingEnabled())) {
  83. return false;
  84. }
  85. if (this._options.shouldCreateSpanForRequest === undefined) {
  86. return true;
  87. }
  88. const cachedDecision = this._createSpanUrlMap.get(url);
  89. if (cachedDecision !== undefined) {
  90. return cachedDecision;
  91. }
  92. const decision = this._options.shouldCreateSpanForRequest(url);
  93. this._createSpanUrlMap.set(url, decision);
  94. return decision;
  95. }
  96. __init4() {this._onRequestCreate = (message) => {
  97. // eslint-disable-next-line deprecation/deprecation
  98. if (!_optionalChain([getClient, 'call', _10 => _10(), 'optionalAccess', _11 => _11.getIntegration, 'call', _12 => _12(Undici)])) {
  99. return;
  100. }
  101. const { request } = message ;
  102. const stringUrl = request.origin ? request.origin.toString() + request.path : request.path;
  103. const client = getClient();
  104. if (!client) {
  105. return;
  106. }
  107. if (isSentryRequestUrl(stringUrl, client) || request.__sentry_span__ !== undefined) {
  108. return;
  109. }
  110. const clientOptions = client.getOptions();
  111. const scope = getCurrentScope();
  112. const isolationScope = getIsolationScope();
  113. const parentSpan = getActiveSpan();
  114. const span = this._shouldCreateSpan(stringUrl) ? createRequestSpan(parentSpan, request, stringUrl) : undefined;
  115. if (span) {
  116. request.__sentry_span__ = span;
  117. }
  118. const shouldAttachTraceData = (url) => {
  119. if (clientOptions.tracePropagationTargets === undefined) {
  120. return true;
  121. }
  122. const cachedDecision = this._headersUrlMap.get(url);
  123. if (cachedDecision !== undefined) {
  124. return cachedDecision;
  125. }
  126. const decision = stringMatchesSomePattern(url, clientOptions.tracePropagationTargets);
  127. this._headersUrlMap.set(url, decision);
  128. return decision;
  129. };
  130. if (shouldAttachTraceData(stringUrl)) {
  131. const { traceId, spanId, sampled, dsc } = {
  132. ...isolationScope.getPropagationContext(),
  133. ...scope.getPropagationContext(),
  134. };
  135. const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled);
  136. const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
  137. dsc ||
  138. (span
  139. ? getDynamicSamplingContextFromSpan(span)
  140. : getDynamicSamplingContextFromClient(traceId, client, scope)),
  141. );
  142. setHeadersOnRequest(request, sentryTraceHeader, sentryBaggageHeader);
  143. }
  144. };}
  145. __init5() {this._onRequestEnd = (message) => {
  146. // eslint-disable-next-line deprecation/deprecation
  147. if (!_optionalChain([getClient, 'call', _13 => _13(), 'optionalAccess', _14 => _14.getIntegration, 'call', _15 => _15(Undici)])) {
  148. return;
  149. }
  150. const { request, response } = message ;
  151. const stringUrl = request.origin ? request.origin.toString() + request.path : request.path;
  152. if (isSentryRequestUrl(stringUrl, getClient())) {
  153. return;
  154. }
  155. const span = request.__sentry_span__;
  156. if (span) {
  157. setHttpStatus(span, response.statusCode);
  158. span.end();
  159. }
  160. if (this._options.breadcrumbs) {
  161. addBreadcrumb(
  162. {
  163. category: 'http',
  164. data: {
  165. method: request.method,
  166. status_code: response.statusCode,
  167. url: stringUrl,
  168. },
  169. type: 'http',
  170. },
  171. {
  172. event: 'response',
  173. request,
  174. response,
  175. },
  176. );
  177. }
  178. };}
  179. __init6() {this._onRequestError = (message) => {
  180. // eslint-disable-next-line deprecation/deprecation
  181. if (!_optionalChain([getClient, 'call', _16 => _16(), 'optionalAccess', _17 => _17.getIntegration, 'call', _18 => _18(Undici)])) {
  182. return;
  183. }
  184. const { request } = message ;
  185. const stringUrl = request.origin ? request.origin.toString() + request.path : request.path;
  186. if (isSentryRequestUrl(stringUrl, getClient())) {
  187. return;
  188. }
  189. const span = request.__sentry_span__;
  190. if (span) {
  191. span.setStatus('internal_error');
  192. span.end();
  193. }
  194. if (this._options.breadcrumbs) {
  195. addBreadcrumb(
  196. {
  197. category: 'http',
  198. data: {
  199. method: request.method,
  200. url: stringUrl,
  201. },
  202. level: 'error',
  203. type: 'http',
  204. },
  205. {
  206. event: 'error',
  207. request,
  208. },
  209. );
  210. }
  211. };}
  212. }Undici.__initStatic();
  213. function setHeadersOnRequest(
  214. request,
  215. sentryTrace,
  216. sentryBaggageHeader,
  217. ) {
  218. const headerLines = request.headers.split('\r\n');
  219. const hasSentryHeaders = headerLines.some(headerLine => headerLine.startsWith('sentry-trace:'));
  220. if (hasSentryHeaders) {
  221. return;
  222. }
  223. request.addHeader('sentry-trace', sentryTrace);
  224. if (sentryBaggageHeader) {
  225. request.addHeader('baggage', sentryBaggageHeader);
  226. }
  227. }
  228. function createRequestSpan(
  229. activeSpan,
  230. request,
  231. stringUrl,
  232. ) {
  233. const url = parseUrl(stringUrl);
  234. const method = request.method || 'GET';
  235. const data = {
  236. 'http.method': method,
  237. };
  238. if (url.search) {
  239. data['http.query'] = url.search;
  240. }
  241. if (url.hash) {
  242. data['http.fragment'] = url.hash;
  243. }
  244. // eslint-disable-next-line deprecation/deprecation
  245. return _optionalChain([activeSpan, 'optionalAccess', _19 => _19.startChild, 'call', _20 => _20({
  246. op: 'http.client',
  247. origin: 'auto.http.node.undici',
  248. description: `${method} ${getSanitizedUrlString(url)}`,
  249. data,
  250. })]);
  251. }
  252. export { ChannelName, Undici, nativeNodeFetchintegration };
  253. //# sourceMappingURL=index.js.map