@sentry/nextjs
Version:
Official Sentry SDK for Next.js
105 lines (95 loc) • 4.47 kB
JavaScript
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