wrapServerComponentWithSentry.js 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { _nullishCoalesce, _optionalChain } from '@sentry/utils';
  2. import { addTracingExtensions, withIsolationScope, getCurrentScope, startSpanManual, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, handleCallbackErrors, captureException } from '@sentry/core';
  3. import { winterCGHeadersToDict, propagationContextFromHeaders } from '@sentry/utils';
  4. import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils.js';
  5. import { commonObjectToPropagationContext } from './utils/commonObjectTracing.js';
  6. import { flushQueue } from './utils/responseEnd.js';
  7. /**
  8. * Wraps an `app` directory server component with Sentry error instrumentation.
  9. */
  10. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  11. function wrapServerComponentWithSentry(
  12. appDirComponent,
  13. context,
  14. ) {
  15. addTracingExtensions();
  16. const { componentRoute, componentType } = context;
  17. // Even though users may define server components as async functions, for the client bundles
  18. // Next.js will turn them into synchronous functions and it will transform any `await`s into instances of the `use`
  19. // hook. 🤯
  20. return new Proxy(appDirComponent, {
  21. apply: (originalFunction, thisArg, args) => {
  22. // TODO: If we ever allow withIsolationScope to take a scope, we should pass a scope here that is shared between all of the server components, similar to what `commonObjectToPropagationContext` does.
  23. return withIsolationScope(isolationScope => {
  24. const completeHeadersDict = context.headers
  25. ? winterCGHeadersToDict(context.headers)
  26. : {};
  27. isolationScope.setSDKProcessingMetadata({
  28. request: {
  29. headers: completeHeadersDict,
  30. },
  31. });
  32. const incomingPropagationContext = propagationContextFromHeaders(
  33. // eslint-disable-next-line deprecation/deprecation
  34. _nullishCoalesce(context.sentryTraceHeader, () => ( completeHeadersDict['sentry-trace'])),
  35. // eslint-disable-next-line deprecation/deprecation
  36. _nullishCoalesce(context.baggageHeader, () => ( completeHeadersDict['baggage'])),
  37. );
  38. const propagationContext = commonObjectToPropagationContext(context.headers, incomingPropagationContext);
  39. isolationScope.setPropagationContext(propagationContext);
  40. getCurrentScope().setPropagationContext(propagationContext);
  41. return startSpanManual(
  42. {
  43. op: 'function.nextjs',
  44. name: `${componentType} Server Component (${componentRoute})`,
  45. attributes: {
  46. [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
  47. [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
  48. },
  49. },
  50. span => {
  51. return handleCallbackErrors(
  52. () => originalFunction.apply(thisArg, args),
  53. error => {
  54. if (isNotFoundNavigationError(error)) {
  55. // We don't want to report "not-found"s
  56. _optionalChain([span, 'optionalAccess', _ => _.setStatus, 'call', _2 => _2('not_found')]);
  57. } else if (isRedirectNavigationError(error)) {
  58. // We don't want to report redirects
  59. _optionalChain([span, 'optionalAccess', _3 => _3.setStatus, 'call', _4 => _4('ok')]);
  60. } else {
  61. _optionalChain([span, 'optionalAccess', _5 => _5.setStatus, 'call', _6 => _6('internal_error')]);
  62. captureException(error, {
  63. mechanism: {
  64. handled: false,
  65. },
  66. });
  67. }
  68. },
  69. () => {
  70. _optionalChain([span, 'optionalAccess', _7 => _7.end, 'call', _8 => _8()]);
  71. // flushQueue should not throw
  72. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  73. flushQueue();
  74. },
  75. );
  76. },
  77. );
  78. });
  79. },
  80. });
  81. }
  82. export { wrapServerComponentWithSentry };
  83. //# sourceMappingURL=wrapServerComponentWithSentry.js.map