fetch.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { hasTracingEnabled, setHttpStatus, getCurrentScope, getClient, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getIsolationScope, spanToTraceHeader, getDynamicSamplingContextFromSpan, getDynamicSamplingContextFromClient } from '@sentry/core';
  2. import { generateSentryTraceHeader, dynamicSamplingContextToSentryBaggageHeader, isInstanceOf, BAGGAGE_HEADER_NAME } from '@sentry/utils';
  3. /**
  4. * Create and track fetch request spans for usage in combination with `addInstrumentationHandler`.
  5. *
  6. * @returns Span if a span was created, otherwise void.
  7. */
  8. function instrumentFetchRequest(
  9. handlerData,
  10. shouldCreateSpan,
  11. shouldAttachHeaders,
  12. spans,
  13. spanOrigin = 'auto.http.browser',
  14. ) {
  15. if (!hasTracingEnabled() || !handlerData.fetchData) {
  16. return undefined;
  17. }
  18. const shouldCreateSpanResult = shouldCreateSpan(handlerData.fetchData.url);
  19. if (handlerData.endTimestamp && shouldCreateSpanResult) {
  20. const spanId = handlerData.fetchData.__span;
  21. if (!spanId) return;
  22. const span = spans[spanId];
  23. if (span) {
  24. if (handlerData.response) {
  25. setHttpStatus(span, handlerData.response.status);
  26. const contentLength =
  27. handlerData.response && handlerData.response.headers && handlerData.response.headers.get('content-length');
  28. if (contentLength) {
  29. const contentLengthNum = parseInt(contentLength);
  30. if (contentLengthNum > 0) {
  31. span.setAttribute('http.response_content_length', contentLengthNum);
  32. }
  33. }
  34. } else if (handlerData.error) {
  35. span.setStatus('internal_error');
  36. }
  37. span.end();
  38. // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
  39. delete spans[spanId];
  40. }
  41. return undefined;
  42. }
  43. const scope = getCurrentScope();
  44. const client = getClient();
  45. const { method, url } = handlerData.fetchData;
  46. const span = shouldCreateSpanResult
  47. ? startInactiveSpan({
  48. name: `${method} ${url}`,
  49. onlyIfParent: true,
  50. attributes: {
  51. url,
  52. type: 'fetch',
  53. 'http.method': method,
  54. [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin,
  55. },
  56. op: 'http.client',
  57. })
  58. : undefined;
  59. if (span) {
  60. handlerData.fetchData.__span = span.spanContext().spanId;
  61. spans[span.spanContext().spanId] = span;
  62. }
  63. if (shouldAttachHeaders(handlerData.fetchData.url) && client) {
  64. const request = handlerData.args[0];
  65. // In case the user hasn't set the second argument of a fetch call we default it to `{}`.
  66. handlerData.args[1] = handlerData.args[1] || {};
  67. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  68. const options = handlerData.args[1];
  69. // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
  70. options.headers = addTracingHeadersToFetchRequest(request, client, scope, options, span);
  71. }
  72. return span;
  73. }
  74. /**
  75. * Adds sentry-trace and baggage headers to the various forms of fetch headers
  76. */
  77. function addTracingHeadersToFetchRequest(
  78. request, // unknown is actually type Request but we can't export DOM types from this package,
  79. client,
  80. scope,
  81. options
  82. ,
  83. requestSpan,
  84. ) {
  85. // eslint-disable-next-line deprecation/deprecation
  86. const span = requestSpan || scope.getSpan();
  87. const isolationScope = getIsolationScope();
  88. const { traceId, spanId, sampled, dsc } = {
  89. ...isolationScope.getPropagationContext(),
  90. ...scope.getPropagationContext(),
  91. };
  92. const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled);
  93. const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
  94. dsc ||
  95. (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client, scope)),
  96. );
  97. const headers =
  98. options.headers ||
  99. (typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request ).headers : undefined);
  100. if (!headers) {
  101. return { 'sentry-trace': sentryTraceHeader, baggage: sentryBaggageHeader };
  102. } else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) {
  103. const newHeaders = new Headers(headers );
  104. newHeaders.append('sentry-trace', sentryTraceHeader);
  105. if (sentryBaggageHeader) {
  106. // If the same header is appended multiple times the browser will merge the values into a single request header.
  107. // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header.
  108. newHeaders.append(BAGGAGE_HEADER_NAME, sentryBaggageHeader);
  109. }
  110. return newHeaders ;
  111. } else if (Array.isArray(headers)) {
  112. const newHeaders = [...headers, ['sentry-trace', sentryTraceHeader]];
  113. if (sentryBaggageHeader) {
  114. // If there are multiple entries with the same key, the browser will merge the values into a single request header.
  115. // Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header.
  116. newHeaders.push([BAGGAGE_HEADER_NAME, sentryBaggageHeader]);
  117. }
  118. return newHeaders ;
  119. } else {
  120. const existingBaggageHeader = 'baggage' in headers ? headers.baggage : undefined;
  121. const newBaggageHeaders = [];
  122. if (Array.isArray(existingBaggageHeader)) {
  123. newBaggageHeaders.push(...existingBaggageHeader);
  124. } else if (existingBaggageHeader) {
  125. newBaggageHeaders.push(existingBaggageHeader);
  126. }
  127. if (sentryBaggageHeader) {
  128. newBaggageHeaders.push(sentryBaggageHeader);
  129. }
  130. return {
  131. ...(headers ),
  132. 'sentry-trace': sentryTraceHeader,
  133. baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined,
  134. };
  135. }
  136. }
  137. export { addTracingHeadersToFetchRequest, instrumentFetchRequest };
  138. //# sourceMappingURL=fetch.js.map