wrapperUtils.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { _optionalChain } from '@sentry/utils';
  2. import { withIsolationScope, continueTrace, startInactiveSpan, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, withActiveSpan, startSpan, captureException, startSpanManual } from '@sentry/core';
  3. import { isString } from '@sentry/utils';
  4. import { platformSupportsStreaming } from './platformSupportsStreaming.js';
  5. import { autoEndSpanOnResponseEnd, flushQueue } from './responseEnd.js';
  6. /**
  7. * Grabs a span off a Next.js datafetcher request object, if it was previously put there via
  8. * `setSpanOnRequest`.
  9. *
  10. * @param req The Next.js datafetcher request object
  11. * @returns the span on the request object if there is one, or `undefined` if the request object didn't have one.
  12. */
  13. function getSpanFromRequest(req) {
  14. return req._sentrySpan;
  15. }
  16. function setSpanOnRequest(transaction, req) {
  17. req._sentrySpan = transaction;
  18. }
  19. /**
  20. * Wraps a function that potentially throws. If it does, the error is passed to `captureException` and rethrown.
  21. *
  22. * Note: This function turns the wrapped function into an asynchronous one.
  23. */
  24. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25. function withErrorInstrumentation(
  26. origFunction,
  27. ) {
  28. return async function ( ...origFunctionArguments) {
  29. try {
  30. return await origFunction.apply(this, origFunctionArguments);
  31. } catch (e) {
  32. // TODO: Extract error logic from `withSentry` in here or create a new wrapper with said logic or something like that.
  33. captureException(e, { mechanism: { handled: false } });
  34. throw e;
  35. }
  36. };
  37. }
  38. /**
  39. * Calls a server-side data fetching function (that takes a `req` and `res` object in its context) with tracing
  40. * instrumentation. A transaction will be created for the incoming request (if it doesn't already exist) in addition to
  41. * a span for the wrapped data fetching function.
  42. *
  43. * All of the above happens in an isolated domain, meaning all thrown errors will be associated with the correct span.
  44. *
  45. * @param origDataFetcher The data fetching method to call.
  46. * @param origFunctionArguments The arguments to call the data fetching method with.
  47. * @param req The data fetching function's request object.
  48. * @param res The data fetching function's response object.
  49. * @param options Options providing details for the created transaction and span.
  50. * @returns what the data fetching method call returned.
  51. */
  52. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  53. function withTracedServerSideDataFetcher(
  54. origDataFetcher,
  55. req,
  56. res,
  57. options
  58. ,
  59. ) {
  60. return async function ( ...args) {
  61. return withIsolationScope(async isolationScope => {
  62. isolationScope.setSDKProcessingMetadata({
  63. request: req,
  64. });
  65. const sentryTrace =
  66. req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
  67. const baggage = _optionalChain([req, 'access', _ => _.headers, 'optionalAccess', _2 => _2.baggage]);
  68. return continueTrace({ sentryTrace, baggage }, () => {
  69. let requestSpan = getSpanFromRequest(req);
  70. if (!requestSpan) {
  71. // TODO(v8): Simplify these checks when startInactiveSpan always returns a span
  72. requestSpan = startInactiveSpan({
  73. name: options.requestedRouteName,
  74. op: 'http.server',
  75. attributes: {
  76. [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
  77. [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
  78. },
  79. });
  80. if (requestSpan) {
  81. requestSpan.setStatus('ok');
  82. setSpanOnRequest(requestSpan, req);
  83. autoEndSpanOnResponseEnd(requestSpan, res);
  84. }
  85. }
  86. const withActiveSpanCallback = () => {
  87. return startSpanManual(
  88. {
  89. op: 'function.nextjs',
  90. name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`,
  91. attributes: {
  92. [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
  93. [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
  94. },
  95. },
  96. async dataFetcherSpan => {
  97. _optionalChain([dataFetcherSpan, 'optionalAccess', _3 => _3.setStatus, 'call', _4 => _4('ok')]);
  98. try {
  99. return await origDataFetcher.apply(this, args);
  100. } catch (e) {
  101. _optionalChain([dataFetcherSpan, 'optionalAccess', _5 => _5.setStatus, 'call', _6 => _6('internal_error')]);
  102. _optionalChain([requestSpan, 'optionalAccess', _7 => _7.setStatus, 'call', _8 => _8('internal_error')]);
  103. throw e;
  104. } finally {
  105. _optionalChain([dataFetcherSpan, 'optionalAccess', _9 => _9.end, 'call', _10 => _10()]);
  106. if (!platformSupportsStreaming()) {
  107. await flushQueue();
  108. }
  109. }
  110. },
  111. );
  112. };
  113. if (requestSpan) {
  114. return withActiveSpan(requestSpan, withActiveSpanCallback);
  115. } else {
  116. return withActiveSpanCallback();
  117. }
  118. });
  119. });
  120. };
  121. }
  122. /**
  123. * Call a data fetcher and trace it. Only traces the function if there is an active transaction on the scope.
  124. *
  125. * We only do the following until we move transaction creation into this function: When called, the wrapped function
  126. * will also update the name of the active transaction with a parameterized route provided via the `options` argument.
  127. */
  128. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  129. async function callDataFetcherTraced(
  130. origFunction,
  131. origFunctionArgs,
  132. options
  133. ,
  134. ) {
  135. const { parameterizedRoute, dataFetchingMethodName } = options;
  136. return startSpan(
  137. {
  138. op: 'function.nextjs',
  139. name: `${dataFetchingMethodName} (${parameterizedRoute})`,
  140. attributes: {
  141. [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
  142. [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
  143. },
  144. },
  145. async dataFetcherSpan => {
  146. _optionalChain([dataFetcherSpan, 'optionalAccess', _11 => _11.setStatus, 'call', _12 => _12('ok')]);
  147. try {
  148. return await origFunction(...origFunctionArgs);
  149. } catch (e) {
  150. _optionalChain([dataFetcherSpan, 'optionalAccess', _13 => _13.setStatus, 'call', _14 => _14('internal_error')]);
  151. captureException(e, { mechanism: { handled: false } });
  152. throw e;
  153. } finally {
  154. _optionalChain([dataFetcherSpan, 'optionalAccess', _15 => _15.end, 'call', _16 => _16()]);
  155. if (!platformSupportsStreaming()) {
  156. await flushQueue();
  157. }
  158. }
  159. },
  160. );
  161. }
  162. export { callDataFetcherTraced, getSpanFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher };
  163. //# sourceMappingURL=wrapperUtils.js.map