@sentry/nextjs
Version:
Official Sentry SDK for Next.js
107 lines (100 loc) • 4.94 kB
JavaScript
import { _nullishCoalesce, _optionalChain } from '@sentry/utils';
import { addTracingExtensions, runWithAsyncContext, continueTrace, getCurrentScope, startSpanManual, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, handleCallbackErrors, captureException, getClient } from '@sentry/core';
import { winterCGHeadersToDict } from '@sentry/utils';
import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils.js';
import { commonObjectToPropagationContext } from './utils/commonObjectTracing.js';
/**
* Wraps a generation function (e.g. generateMetadata) with Sentry error and performance instrumentation.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function wrapGenerationFunctionWithSentry(
generationFunction,
context,
) {
addTracingExtensions();
const { requestAsyncStorage, componentRoute, componentType, generationFunctionIdentifier } = context;
return new Proxy(generationFunction, {
apply: (originalFunction, thisArg, args) => {
let headers = undefined;
// We try-catch here just in case anything goes wrong with the async storage here goes wrong since it is Next.js internal API
try {
headers = _optionalChain([requestAsyncStorage, 'optionalAccess', _ => _.getStore, 'call', _2 => _2(), 'optionalAccess', _3 => _3.headers]);
} catch (e) {
/** empty */
}
let data = undefined;
if (_optionalChain([getClient, 'call', _4 => _4(), 'optionalAccess', _5 => _5.getOptions, 'call', _6 => _6(), 'access', _7 => _7.sendDefaultPii])) {
const props = args[0];
const params = props && typeof props === 'object' && 'params' in props ? props.params : undefined;
const searchParams =
props && typeof props === 'object' && 'searchParams' in props ? props.searchParams : undefined;
data = { params, searchParams };
}
return runWithAsyncContext(() => {
const transactionContext = continueTrace({
baggage: _optionalChain([headers, 'optionalAccess', _8 => _8.get, 'call', _9 => _9('baggage')]),
sentryTrace: _nullishCoalesce(_optionalChain([headers, 'optionalAccess', _10 => _10.get, 'call', _11 => _11('sentry-trace')]), () => ( undefined)),
});
// 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(
headers,
propagationContext,
);
transactionContext.traceId = commonTraceId;
transactionContext.parentSpanId = commonSpanId;
}
return startSpanManual(
{
op: 'function.nextjs',
name: `${componentType}.${generationFunctionIdentifier} (${componentRoute})`,
origin: 'auto.function.nextjs',
...transactionContext,
data,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
},
metadata: {
// eslint-disable-next-line deprecation/deprecation
...transactionContext.metadata,
request: {
headers: headers ? winterCGHeadersToDict(headers) : undefined,
},
},
},
span => {
return handleCallbackErrors(
() => originalFunction.apply(thisArg, args),
err => {
if (isNotFoundNavigationError(err)) {
// We don't want to report "not-found"s
_optionalChain([span, 'optionalAccess', _12 => _12.setStatus, 'call', _13 => _13('not_found')]);
} else if (isRedirectNavigationError(err)) {
// We don't want to report redirects
_optionalChain([span, 'optionalAccess', _14 => _14.setStatus, 'call', _15 => _15('ok')]);
} else {
_optionalChain([span, 'optionalAccess', _16 => _16.setStatus, 'call', _17 => _17('internal_error')]);
captureException(err, {
mechanism: {
handled: false,
data: {
function: 'wrapGenerationFunctionWithSentry',
},
},
});
}
},
() => {
_optionalChain([span, 'optionalAccess', _18 => _18.end, 'call', _19 => _19()]);
},
);
},
);
});
},
});
}
export { wrapGenerationFunctionWithSentry };
//# sourceMappingURL=wrapGenerationFunctionWithSentry.js.map