wrapperUtils.js 6.8 KB

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