UNPKG

@sentry/nextjs

Version:
105 lines (95 loc) • 4.47 kB
import { _nullishCoalesce, _optionalChain } from '@sentry/utils'; import { addTracingExtensions, runWithAsyncContext, continueTrace, getCurrentScope, startSpanManual, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, handleCallbackErrors, captureException } from '@sentry/core'; import { winterCGHeadersToDict } from '@sentry/utils'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils.js'; import { commonObjectToPropagationContext } from './utils/commonObjectTracing.js'; import { flushQueue } from './utils/responseEnd.js'; /** * Wraps an `app` directory server component with Sentry error instrumentation. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function wrapServerComponentWithSentry( appDirComponent, context, ) { addTracingExtensions(); const { componentRoute, componentType } = context; // Even though users may define server components as async functions, for the client bundles // Next.js will turn them into synchronous functions and it will transform any `await`s into instances of the `use` // hook. 🤯 return new Proxy(appDirComponent, { apply: (originalFunction, thisArg, args) => { return runWithAsyncContext(() => { const completeHeadersDict = context.headers ? winterCGHeadersToDict(context.headers) : {}; const transactionContext = continueTrace({ // eslint-disable-next-line deprecation/deprecation sentryTrace: _nullishCoalesce(context.sentryTraceHeader, () => ( completeHeadersDict['sentry-trace'])), // eslint-disable-next-line deprecation/deprecation baggage: _nullishCoalesce(context.baggageHeader, () => ( completeHeadersDict['baggage'])), }); // If there is no incoming trace, we are setting the transaction context to one that is shared between all other // transactions for this request. We do this based on the `headers` object, which is the same for all components. const propagationContext = getCurrentScope().getPropagationContext(); if (!transactionContext.traceId && !transactionContext.parentSpanId) { const { traceId: commonTraceId, spanId: commonSpanId } = commonObjectToPropagationContext( context.headers, propagationContext, ); transactionContext.traceId = commonTraceId; transactionContext.parentSpanId = commonSpanId; } const res = startSpanManual( { ...transactionContext, op: 'function.nextjs', name: `${componentType} Server Component (${componentRoute})`, status: 'ok', origin: 'auto.function.nextjs', attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', }, metadata: { // eslint-disable-next-line deprecation/deprecation ...transactionContext.metadata, request: { headers: completeHeadersDict, }, }, }, span => { return handleCallbackErrors( () => originalFunction.apply(thisArg, args), error => { if (isNotFoundNavigationError(error)) { // We don't want to report "not-found"s _optionalChain([span, 'optionalAccess', _ => _.setStatus, 'call', _2 => _2('not_found')]); } else if (isRedirectNavigationError(error)) { // We don't want to report redirects _optionalChain([span, 'optionalAccess', _3 => _3.setStatus, 'call', _4 => _4('ok')]); } else { _optionalChain([span, 'optionalAccess', _5 => _5.setStatus, 'call', _6 => _6('internal_error')]); captureException(error, { mechanism: { handled: false, }, }); } }, () => { _optionalChain([span, 'optionalAccess', _7 => _7.end, 'call', _8 => _8()]); // flushQueue should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises flushQueue(); }, ); }, ); return res; }); }, }); } export { wrapServerComponentWithSentry }; //# sourceMappingURL=wrapServerComponentWithSentry.js.map