withServerActionInstrumentation.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { _nullishCoalesce, _optionalChain } from '@sentry/utils';
  2. import { addTracingExtensions, withIsolationScope, continueTrace, startSpan, handleCallbackErrors, captureException, getIsolationScope, getClient } from '@sentry/core';
  3. import { logger } from '@sentry/utils';
  4. import { DEBUG_BUILD } from './debug-build.js';
  5. import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils.js';
  6. import { platformSupportsStreaming } from './utils/platformSupportsStreaming.js';
  7. import { flushQueue } from './utils/responseEnd.js';
  8. /**
  9. * Wraps a Next.js Server Action implementation with Sentry Error and Performance instrumentation.
  10. */
  11. function withServerActionInstrumentation(
  12. ...args
  13. ) {
  14. if (typeof args[1] === 'function') {
  15. const [serverActionName, callback] = args;
  16. return withServerActionInstrumentationImplementation(serverActionName, {}, callback);
  17. } else {
  18. const [serverActionName, options, callback] = args;
  19. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  20. return withServerActionInstrumentationImplementation(serverActionName, options, callback);
  21. }
  22. }
  23. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  24. async function withServerActionInstrumentationImplementation(
  25. serverActionName,
  26. options,
  27. callback,
  28. ) {
  29. addTracingExtensions();
  30. return withIsolationScope(isolationScope => {
  31. const sendDefaultPii = _optionalChain([getClient, 'call', _ => _(), 'optionalAccess', _2 => _2.getOptions, 'call', _3 => _3(), 'access', _4 => _4.sendDefaultPii]);
  32. let sentryTraceHeader;
  33. let baggageHeader;
  34. const fullHeadersObject = {};
  35. try {
  36. sentryTraceHeader = _nullishCoalesce(_optionalChain([options, 'access', _5 => _5.headers, 'optionalAccess', _6 => _6.get, 'call', _7 => _7('sentry-trace')]), () => ( undefined));
  37. baggageHeader = _optionalChain([options, 'access', _8 => _8.headers, 'optionalAccess', _9 => _9.get, 'call', _10 => _10('baggage')]);
  38. _optionalChain([options, 'access', _11 => _11.headers, 'optionalAccess', _12 => _12.forEach, 'call', _13 => _13((value, key) => {
  39. fullHeadersObject[key] = value;
  40. })]);
  41. } catch (e) {
  42. DEBUG_BUILD &&
  43. logger.warn(
  44. "Sentry wasn't able to extract the tracing headers for a server action. Will not trace this request.",
  45. );
  46. }
  47. isolationScope.setSDKProcessingMetadata({
  48. request: {
  49. headers: fullHeadersObject,
  50. },
  51. });
  52. return continueTrace(
  53. {
  54. sentryTrace: sentryTraceHeader,
  55. baggage: baggageHeader,
  56. },
  57. async () => {
  58. try {
  59. return await startSpan(
  60. {
  61. op: 'function.server_action',
  62. name: `serverAction/${serverActionName}`,
  63. metadata: {
  64. source: 'route',
  65. },
  66. },
  67. async span => {
  68. const result = await handleCallbackErrors(callback, error => {
  69. if (isNotFoundNavigationError(error)) {
  70. // We don't want to report "not-found"s
  71. _optionalChain([span, 'optionalAccess', _14 => _14.setStatus, 'call', _15 => _15('not_found')]);
  72. } else if (isRedirectNavigationError(error)) {
  73. // Don't do anything for redirects
  74. } else {
  75. _optionalChain([span, 'optionalAccess', _16 => _16.setStatus, 'call', _17 => _17('internal_error')]);
  76. captureException(error, {
  77. mechanism: {
  78. handled: false,
  79. },
  80. });
  81. }
  82. });
  83. if (options.recordResponse !== undefined ? options.recordResponse : sendDefaultPii) {
  84. getIsolationScope().setExtra('server_action_result', result);
  85. }
  86. if (options.formData) {
  87. options.formData.forEach((value, key) => {
  88. getIsolationScope().setExtra(
  89. `server_action_form_data.${key}`,
  90. typeof value === 'string' ? value : '[non-string value]',
  91. );
  92. });
  93. }
  94. return result;
  95. },
  96. );
  97. } finally {
  98. if (!platformSupportsStreaming()) {
  99. // Lambdas require manual flushing to prevent execution freeze before the event is sent
  100. await flushQueue();
  101. }
  102. if (process.env.NEXT_RUNTIME === 'edge') {
  103. // flushQueue should not throw
  104. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  105. flushQueue();
  106. }
  107. }
  108. },
  109. );
  110. });
  111. }
  112. export { withServerActionInstrumentation };
  113. //# sourceMappingURL=withServerActionInstrumentation.js.map