var { _optionalChain } = require('@sentry/utils'); Object.defineProperty(exports, '__esModule', { value: true }); const core = require('@sentry/core'); const utils = require('@sentry/utils'); const platformSupportsStreaming = require('./platformSupportsStreaming.js'); const responseEnd = require('./responseEnd.js'); /** * Grabs a span off a Next.js datafetcher request object, if it was previously put there via * `setSpanOnRequest`. * * @param req The Next.js datafetcher request object * @returns the span on the request object if there is one, or `undefined` if the request object didn't have one. */ function getSpanFromRequest(req) { return req._sentrySpan; } function setSpanOnRequest(transaction, req) { req._sentrySpan = transaction; } /** * Wraps a function that potentially throws. If it does, the error is passed to `captureException` and rethrown. * * Note: This function turns the wrapped function into an asynchronous one. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function withErrorInstrumentation( origFunction, ) { return async function ( ...origFunctionArguments) { try { return await origFunction.apply(this, origFunctionArguments); } catch (e) { // TODO: Extract error logic from `withSentry` in here or create a new wrapper with said logic or something like that. core.captureException(e, { mechanism: { handled: false } }); throw e; } }; } /** * Calls a server-side data fetching function (that takes a `req` and `res` object in its context) with tracing * instrumentation. A transaction will be created for the incoming request (if it doesn't already exist) in addition to * a span for the wrapped data fetching function. * * All of the above happens in an isolated domain, meaning all thrown errors will be associated with the correct span. * * @param origDataFetcher The data fetching method to call. * @param origFunctionArguments The arguments to call the data fetching method with. * @param req The data fetching function's request object. * @param res The data fetching function's response object. * @param options Options providing details for the created transaction and span. * @returns what the data fetching method call returned. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function withTracedServerSideDataFetcher( origDataFetcher, req, res, options , ) { return async function ( ...args) { return core.withIsolationScope(async isolationScope => { isolationScope.setSDKProcessingMetadata({ request: req, }); const sentryTrace = req.headers && utils.isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined; const baggage = _optionalChain([req, 'access', _ => _.headers, 'optionalAccess', _2 => _2.baggage]); return core.continueTrace({ sentryTrace, baggage }, () => { let requestSpan = getSpanFromRequest(req); if (!requestSpan) { // TODO(v8): Simplify these checks when startInactiveSpan always returns a span requestSpan = core.startInactiveSpan({ name: options.requestedRouteName, op: 'http.server', attributes: { [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, }); if (requestSpan) { requestSpan.setStatus('ok'); setSpanOnRequest(requestSpan, req); responseEnd.autoEndSpanOnResponseEnd(requestSpan, res); } } const withActiveSpanCallback = () => { return core.startSpanManual( { op: 'function.nextjs', name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`, attributes: { [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, }, async dataFetcherSpan => { _optionalChain([dataFetcherSpan, 'optionalAccess', _3 => _3.setStatus, 'call', _4 => _4('ok')]); try { return await origDataFetcher.apply(this, args); } catch (e) { _optionalChain([dataFetcherSpan, 'optionalAccess', _5 => _5.setStatus, 'call', _6 => _6('internal_error')]); _optionalChain([requestSpan, 'optionalAccess', _7 => _7.setStatus, 'call', _8 => _8('internal_error')]); throw e; } finally { _optionalChain([dataFetcherSpan, 'optionalAccess', _9 => _9.end, 'call', _10 => _10()]); if (!platformSupportsStreaming.platformSupportsStreaming()) { await responseEnd.flushQueue(); } } }, ); }; if (requestSpan) { return core.withActiveSpan(requestSpan, withActiveSpanCallback); } else { return withActiveSpanCallback(); } }); }); }; } /** * Call a data fetcher and trace it. Only traces the function if there is an active transaction on the scope. * * We only do the following until we move transaction creation into this function: When called, the wrapped function * will also update the name of the active transaction with a parameterized route provided via the `options` argument. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any async function callDataFetcherTraced( origFunction, origFunctionArgs, options , ) { const { parameterizedRoute, dataFetchingMethodName } = options; return core.startSpan( { op: 'function.nextjs', name: `${dataFetchingMethodName} (${parameterizedRoute})`, attributes: { [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, }, async dataFetcherSpan => { _optionalChain([dataFetcherSpan, 'optionalAccess', _11 => _11.setStatus, 'call', _12 => _12('ok')]); try { return await origFunction(...origFunctionArgs); } catch (e) { _optionalChain([dataFetcherSpan, 'optionalAccess', _13 => _13.setStatus, 'call', _14 => _14('internal_error')]); core.captureException(e, { mechanism: { handled: false } }); throw e; } finally { _optionalChain([dataFetcherSpan, 'optionalAccess', _15 => _15.end, 'call', _16 => _16()]); if (!platformSupportsStreaming.platformSupportsStreaming()) { await responseEnd.flushQueue(); } } }, ); } exports.callDataFetcherTraced = callDataFetcherTraced; exports.getSpanFromRequest = getSpanFromRequest; exports.withErrorInstrumentation = withErrorInstrumentation; exports.withTracedServerSideDataFetcher = withTracedServerSideDataFetcher; //# sourceMappingURL=wrapperUtils.js.map