diff --git a/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts b/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts index 59d49856fb7b..52bd696ece16 100644 --- a/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts +++ b/packages/nextjs/src/config/templates/apiProxyLoaderTemplate.ts @@ -12,9 +12,9 @@ import * as origModule from '__RESOURCE_PATH__'; import * as Sentry from '@sentry/nextjs'; import type { PageConfig } from 'next'; -// We import this from `withSentry` rather than directly from `next` because our version can work simultaneously with -// multiple versions of next. See note in `withSentry` for more. -import type { NextApiHandler } from '../../utils/withSentry'; +// We import this from `wrappers` rather than directly from `next` because our version can work simultaneously with +// multiple versions of next. See note in `wrappers/types` for more. +import type { NextApiHandler } from '../wrappers'; type NextApiModule = { default: NextApiHandler; diff --git a/packages/nextjs/src/config/wrappers/index.ts b/packages/nextjs/src/config/wrappers/index.ts index 9f710d1f5104..aa406b970e77 100644 --- a/packages/nextjs/src/config/wrappers/index.ts +++ b/packages/nextjs/src/config/wrappers/index.ts @@ -1,7 +1,9 @@ +export type { AugmentedNextApiResponse, NextApiHandler, WrappedNextApiHandler } from './types'; + export { withSentryGetStaticProps } from './withSentryGetStaticProps'; export { withSentryServerSideGetInitialProps } from './withSentryServerSideGetInitialProps'; export { withSentryServerSideAppGetInitialProps } from './withSentryServerSideAppGetInitialProps'; export { withSentryServerSideDocumentGetInitialProps } from './withSentryServerSideDocumentGetInitialProps'; export { withSentryServerSideErrorGetInitialProps } from './withSentryServerSideErrorGetInitialProps'; export { withSentryGetServerSideProps } from './withSentryGetServerSideProps'; -export { withSentryAPI } from './withSentryAPI'; +export { withSentry, withSentryAPI } from './withSentryAPI'; diff --git a/packages/nextjs/src/config/wrappers/types.ts b/packages/nextjs/src/config/wrappers/types.ts new file mode 100644 index 000000000000..d6e3534679ab --- /dev/null +++ b/packages/nextjs/src/config/wrappers/types.ts @@ -0,0 +1,45 @@ +import type { Transaction } from '@sentry/types'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +// The `NextApiHandler` and `WrappedNextApiHandler` types are the same as the official `NextApiHandler` type, except: +// +// a) The wrapped version returns only promises, because wrapped handlers are always async. +// +// b) Instead of having a return types based on `void` (Next < 12.1.6) or `unknown` (Next 12.1.6+), both the wrapped and +// unwrapped versions of the type have both. This doesn't matter to users, because they exist solely on one side of that +// version divide or the other. For us, though, it's entirely possible to have one version of Next installed in our +// local repo (as a dev dependency) and have another Next version installed in a test app which also has the local SDK +// linked in. +// +// In that case, if those two versions are on either side of the 12.1.6 divide, importing the official `NextApiHandler` +// type here would break the test app's build, because it would set up a situation in which the linked SDK's +// `withSentry` would refer to one version of the type (from the local repo's `node_modules`) while any typed handler in +// the test app would refer to the other version of the type (from the test app's `node_modules`). By using a custom +// version of the type compatible with both the old and new official versions, we can use any Next version we want in a +// test app without worrying about type errors. +// +// c) These have internal SDK flags which the official Next types obviously don't have, one to allow our auto-wrapping +// function, `withSentryAPI`, to pass the parameterized route into `withSentry`, and the other to prevent a manually +// wrapped route from being wrapped again by the auto-wrapper. + +export type NextApiHandler = { + __sentry_route__?: string; + (req: NextApiRequest, res: NextApiResponse): void | Promise | unknown | Promise; +}; + +export type WrappedNextApiHandler = { + __sentry_route__?: string; + __sentry_wrapped__?: boolean; + (req: NextApiRequest, res: NextApiResponse): Promise | Promise; +}; + +export type AugmentedNextApiRequest = NextApiRequest & { + __withSentry_applied__?: boolean; +}; + +export type AugmentedNextApiResponse = NextApiResponse & { + __sentryTransaction?: Transaction; +}; + +export type ResponseEndMethod = AugmentedNextApiResponse['end']; +export type WrappedResponseEndMethod = AugmentedNextApiResponse['end']; diff --git a/packages/nextjs/src/config/wrappers/withSentryAPI.ts b/packages/nextjs/src/config/wrappers/withSentryAPI.ts index 1dd095b90697..5a469703c151 100644 --- a/packages/nextjs/src/config/wrappers/withSentryAPI.ts +++ b/packages/nextjs/src/config/wrappers/withSentryAPI.ts @@ -1,8 +1,24 @@ +import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node'; +import { extractTraceparentData, hasTracingEnabled } from '@sentry/tracing'; +import { + addExceptionMechanism, + baggageHeaderToDynamicSamplingContext, + isString, + logger, + objectify, + stripUrlQueryAndFragment, +} from '@sentry/utils'; +import * as domain from 'domain'; + import { formatAsCode, nextLogger } from '../../utils/nextLogger'; -// We import these types from `withSentry` rather than directly from `next` because our version can work simultaneously -// with multiple versions of next. See note in `withSentry` for more. -import type { NextApiHandler, WrappedNextApiHandler } from '../../utils/withSentry'; -import { withSentry } from '../../utils/withSentry'; +import type { + AugmentedNextApiRequest, + AugmentedNextApiResponse, + NextApiHandler, + ResponseEndMethod, + WrappedNextApiHandler, + WrappedResponseEndMethod, +} from './types'; /** * Wrap the given API route handler for tracing and error capturing. Thin wrapper around `withSentry`, which only @@ -37,3 +53,207 @@ export function withSentryAPI( return withSentry(maybeWrappedHandler, parameterizedRoute); } + +/** + * Legacy function for manually wrapping API route handlers, now used as the innards of `withSentryAPI`. + * + * @param origHandler The user's original API route handler + * @param parameterizedRoute The route whose handler is being wrapped. Meant for internal use only. + * @returns A wrapped version of the handler + */ +export function withSentry(origHandler: NextApiHandler, parameterizedRoute?: string): WrappedNextApiHandler { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + return async function sentryWrappedHandler(req: AugmentedNextApiRequest, res: AugmentedNextApiResponse) { + // We're now auto-wrapping API route handlers using `withSentryAPI` (which uses `withSentry` under the hood), but + // users still may have their routes manually wrapped with `withSentry`. This check makes `sentryWrappedHandler` + // idempotent so that those cases don't break anything. + if (req.__withSentry_applied__) { + return origHandler(req, res); + } + req.__withSentry_applied__ = true; + + // first order of business: monkeypatch `res.end()` so that it will wait for us to send events to sentry before it + // fires (if we don't do this, the lambda will close too early and events will be either delayed or lost) + // eslint-disable-next-line @typescript-eslint/unbound-method + res.end = wrapEndMethod(res.end); + + // use a domain in order to prevent scope bleed between requests + const local = domain.create(); + local.add(req); + local.add(res); + + // `local.bind` causes everything to run inside a domain, just like `local.run` does, but it also lets the callback + // return a value. In our case, all any of the codepaths return is a promise of `void`, but nextjs still counts on + // getting that before it will finish the response. + const boundHandler = local.bind(async () => { + const currentScope = getCurrentHub().getScope(); + + if (currentScope) { + currentScope.setSDKProcessingMetadata({ request: req }); + + if (hasTracingEnabled()) { + // If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision) + let traceparentData; + if (req.headers && isString(req.headers['sentry-trace'])) { + traceparentData = extractTraceparentData(req.headers['sentry-trace']); + __DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`); + } + + const baggageHeader = req.headers && req.headers.baggage; + const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader); + + // prefer the parameterized route, if we have it (which we will if we've auto-wrapped the route handler) + let reqPath = parameterizedRoute; + + // If not, fake it by just replacing parameter values with their names, hoping that none of them match either + // each other or any hard-coded parts of the path + if (!reqPath) { + const url = `${req.url}`; + // pull off query string, if any + reqPath = stripUrlQueryAndFragment(url); + // Replace with placeholder + if (req.query) { + for (const [key, value] of Object.entries(req.query)) { + reqPath = reqPath.replace(`${value}`, `[${key}]`); + } + } + } + + const reqMethod = `${(req.method || 'GET').toUpperCase()} `; + + const transaction = startTransaction( + { + name: `${reqMethod}${reqPath}`, + op: 'http.server', + ...traceparentData, + metadata: { + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, + source: 'route', + request: req, + }, + }, + // extra context passed to the `tracesSampler` + { request: req }, + ); + currentScope.setSpan(transaction); + + // save a link to the transaction on the response, so that even if there's an error (landing us outside of + // the domain), we can still finish it (albeit possibly missing some scope data) + res.__sentryTransaction = transaction; + } + } + + try { + const handlerResult = await origHandler(req, res); + + if (process.env.NODE_ENV === 'development' && !process.env.SENTRY_IGNORE_API_RESOLUTION_ERROR) { + // eslint-disable-next-line no-console + console.warn( + `[sentry] If Next.js logs a warning "API resolved without sending a response", it's a false positive, which we're working to rectify. + In the meantime, to suppress this warning, set \`SENTRY_IGNORE_API_RESOLUTION_ERROR\` to 1 in your env. + To suppress the nextjs warning, use the \`externalResolver\` API route option (see https://nextjs.org/docs/api-routes/api-middlewares#custom-config for details).`, + ); + } + + return handlerResult; + } catch (e) { + // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can + // store a seen flag on it. (Because of the one-way-on-Vercel-one-way-off-of-Vercel approach we've been forced + // to take, it can happen that the same thrown object gets caught in two different ways, and flagging it is a + // way to prevent it from actually being reported twice.) + const objectifiedErr = objectify(e); + + if (currentScope) { + currentScope.addEventProcessor(event => { + addExceptionMechanism(event, { + type: 'instrument', + handled: true, + data: { + wrapped_handler: origHandler.name, + function: 'withSentry', + }, + }); + return event; + }); + + captureException(objectifiedErr); + } + + // Because we're going to finish and send the transaction before passing the error onto nextjs, it won't yet + // have had a chance to set the status to 500, so unless we do it ourselves now, we'll incorrectly report that + // the transaction was error-free + res.statusCode = 500; + res.statusMessage = 'Internal Server Error'; + + // Make sure we have a chance to finish the transaction and flush events to Sentry before the handler errors + // out. (Apps which are deployed on Vercel run their API routes in lambdas, and those lambdas will shut down the + // moment they detect an error, so it's important to get this done before rethrowing the error. Apps not + // deployed serverlessly will run into this cleanup function again in `res.end(), but it'll just no-op.) + await finishSentryProcessing(res); + + // We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it + // would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark + // the error as already having been captured.) + throw objectifiedErr; + } + }); + + // Since API route handlers are all async, nextjs always awaits the return value (meaning it's fine for us to return + // a promise here rather than a real result, and it saves us the overhead of an `await` call.) + return boundHandler(); + }; +} + +/** + * Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish. + * + * Note: This wraps a sync method with an async method. While in general that's not a great idea in terms of keeping + * things in the right order, in this case it's safe, because the native `.end()` actually *is* async, and its run + * actually *is* awaited, just manually so (which reflects the fact that the core of the request/response code in Node + * by far predates the introduction of `async`/`await`). When `.end()` is done, it emits the `prefinish` event, and + * only once that fires does request processing continue. See + * https://github.com/nodejs/node/commit/7c9b607048f13741173d397795bac37707405ba7. + * + * @param origEnd The original `res.end()` method + * @returns The wrapped version + */ +function wrapEndMethod(origEnd: ResponseEndMethod): WrappedResponseEndMethod { + return async function newEnd(this: AugmentedNextApiResponse, ...args: unknown[]) { + await finishSentryProcessing(this); + + return origEnd.call(this, ...args); + }; +} + +/** + * Close the open transaction (if any) and flush events to Sentry. + * + * @param res The outgoing response for this request, on which the transaction is stored + */ +async function finishSentryProcessing(res: AugmentedNextApiResponse): Promise { + const { __sentryTransaction: transaction } = res; + + if (transaction) { + transaction.setHttpStatus(res.statusCode); + + // Push `transaction.finish` to the next event loop so open spans have a better chance of finishing before the + // transaction closes, and make sure to wait until that's done before flushing events + const transactionFinished: Promise = new Promise(resolve => { + setImmediate(() => { + transaction.finish(); + resolve(); + }); + }); + await transactionFinished; + } + + // Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda + // ends. If there was an error, rethrow it so that the normal exception-handling mechanisms can apply. + try { + __DEBUG_BUILD__ && logger.log('Flushing events...'); + await flush(2000); + __DEBUG_BUILD__ && logger.log('Done flushing events'); + } catch (e) { + __DEBUG_BUILD__ && logger.log('Error while flushing events:\n', e); + } +} diff --git a/packages/nextjs/src/index.server.ts b/packages/nextjs/src/index.server.ts index 28442999b2aa..49e868154a74 100644 --- a/packages/nextjs/src/index.server.ts +++ b/packages/nextjs/src/index.server.ts @@ -135,8 +135,8 @@ export { withSentryServerSideDocumentGetInitialProps, withSentryServerSideErrorGetInitialProps, withSentryAPI, + withSentry, } from './config/wrappers'; -export { withSentry } from './utils/withSentry'; // Wrap various server methods to enable error monitoring and tracing. (Note: This only happens for non-Vercel // deployments, because the current method of doing the wrapping a) crashes Next 12 apps deployed to Vercel and diff --git a/packages/nextjs/src/utils/withSentry.ts b/packages/nextjs/src/utils/withSentry.ts deleted file mode 100644 index bd094d58d8c8..000000000000 --- a/packages/nextjs/src/utils/withSentry.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node'; -import { extractTraceparentData, hasTracingEnabled } from '@sentry/tracing'; -import { Transaction } from '@sentry/types'; -import { - addExceptionMechanism, - baggageHeaderToDynamicSamplingContext, - isString, - logger, - objectify, - stripUrlQueryAndFragment, -} from '@sentry/utils'; -import * as domain from 'domain'; -import { NextApiRequest, NextApiResponse } from 'next'; - -// These are the same as the official `NextApiHandler` type, except -// -// a) The wrapped version returns only promises, because wrapped handlers are always async. -// -// b) Instead of having a return types based on `void` (Next < 12.1.6) or `unknown` (Next 12.1.6+), both the wrapped and -// unwrapped versions of the type have both. This doesn't matter to users, because they exist solely on one side of that -// version divide or the other. For us, though, it's entirely possible to have one version of Next installed in our -// local repo (as a dev dependency) and have another Next version installed in a test app which also has the local SDK -// linked in. -// -// In that case, if those two versions are on either side of the 12.1.6 divide, importing the official `NextApiHandler` -// type here would break the test app's build, because it would set up a situation in which the linked SDK's -// `withSentry` would refer to one version of the type (from the local repo's `node_modules`) while any typed handler in -// the test app would refer to the other version of the type (from the test app's `node_modules`). By using a custom -// version of the type compatible with both the old and new official versions, we can use any Next version we want in -// a test app without worrying about type errors. -export type NextApiHandler = ( - req: NextApiRequest, - res: NextApiResponse, -) => void | Promise | unknown | Promise; -export type WrappedNextApiHandler = (req: NextApiRequest, res: NextApiResponse) => Promise | Promise; - -type AugmentedNextApiRequest = NextApiRequest & { - __withSentry_applied__?: boolean; -}; - -export type AugmentedNextApiResponse = NextApiResponse & { - __sentryTransaction?: Transaction; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const withSentry = (origHandler: NextApiHandler, parameterizedRoute?: string): WrappedNextApiHandler => { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - return async function sentryWrappedHandler(req: AugmentedNextApiRequest, res: NextApiResponse) { - // We're now auto-wrapping API route handlers using `withSentryAPI` (which uses `withSentry` under the hood), but - // users still may have their routes manually wrapped with `withSentry`. This check makes `sentryWrappedHandler` - // idempotent so that those cases don't break anything. - if (req.__withSentry_applied__) { - return origHandler(req, res); - } - req.__withSentry_applied__ = true; - - // first order of business: monkeypatch `res.end()` so that it will wait for us to send events to sentry before it - // fires (if we don't do this, the lambda will close too early and events will be either delayed or lost) - // eslint-disable-next-line @typescript-eslint/unbound-method - res.end = wrapEndMethod(res.end); - - // use a domain in order to prevent scope bleed between requests - const local = domain.create(); - local.add(req); - local.add(res); - - // `local.bind` causes everything to run inside a domain, just like `local.run` does, but it also lets the callback - // return a value. In our case, all any of the codepaths return is a promise of `void`, but nextjs still counts on - // getting that before it will finish the response. - const boundHandler = local.bind(async () => { - const currentScope = getCurrentHub().getScope(); - - if (currentScope) { - currentScope.setSDKProcessingMetadata({ request: req }); - - if (hasTracingEnabled()) { - // If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision) - let traceparentData; - if (req.headers && isString(req.headers['sentry-trace'])) { - traceparentData = extractTraceparentData(req.headers['sentry-trace']); - __DEBUG_BUILD__ && logger.log(`[Tracing] Continuing trace ${traceparentData?.traceId}.`); - } - - const baggageHeader = req.headers && req.headers.baggage; - const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader); - - // prefer the parameterized route, if we have it (which we will if we've auto-wrapped the route handler) - let reqPath = parameterizedRoute; - - // If not, fake it by just replacing parameter values with their names, hoping that none of them match either - // each other or any hard-coded parts of the path - if (!reqPath) { - const url = `${req.url}`; - // pull off query string, if any - reqPath = stripUrlQueryAndFragment(url); - // Replace with placeholder - if (req.query) { - for (const [key, value] of Object.entries(req.query)) { - reqPath = reqPath.replace(`${value}`, `[${key}]`); - } - } - } - - const reqMethod = `${(req.method || 'GET').toUpperCase()} `; - - const transaction = startTransaction( - { - name: `${reqMethod}${reqPath}`, - op: 'http.server', - ...traceparentData, - metadata: { - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, - source: 'route', - request: req, - }, - }, - // extra context passed to the `tracesSampler` - { request: req }, - ); - currentScope.setSpan(transaction); - - // save a link to the transaction on the response, so that even if there's an error (landing us outside of - // the domain), we can still finish it (albeit possibly missing some scope data) - (res as AugmentedNextApiResponse).__sentryTransaction = transaction; - } - } - - try { - const handlerResult = await origHandler(req, res); - - if (process.env.NODE_ENV === 'development' && !process.env.SENTRY_IGNORE_API_RESOLUTION_ERROR) { - // eslint-disable-next-line no-console - console.warn( - `[sentry] If Next.js logs a warning "API resolved without sending a response", it's a false positive, which we're working to rectify. - In the meantime, to suppress this warning, set \`SENTRY_IGNORE_API_RESOLUTION_ERROR\` to 1 in your env. - To suppress the nextjs warning, use the \`externalResolver\` API route option (see https://nextjs.org/docs/api-routes/api-middlewares#custom-config for details).`, - ); - } - - return handlerResult; - } catch (e) { - // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can - // store a seen flag on it. (Because of the one-way-on-Vercel-one-way-off-of-Vercel approach we've been forced - // to take, it can happen that the same thrown object gets caught in two different ways, and flagging it is a - // way to prevent it from actually being reported twice.) - const objectifiedErr = objectify(e); - - if (currentScope) { - currentScope.addEventProcessor(event => { - addExceptionMechanism(event, { - type: 'instrument', - handled: true, - data: { - wrapped_handler: origHandler.name, - function: 'withSentry', - }, - }); - return event; - }); - - captureException(objectifiedErr); - } - - // Because we're going to finish and send the transaction before passing the error onto nextjs, it won't yet - // have had a chance to set the status to 500, so unless we do it ourselves now, we'll incorrectly report that - // the transaction was error-free - res.statusCode = 500; - res.statusMessage = 'Internal Server Error'; - - // Make sure we have a chance to finish the transaction and flush events to Sentry before the handler errors - // out. (Apps which are deployed on Vercel run their API routes in lambdas, and those lambdas will shut down the - // moment they detect an error, so it's important to get this done before rethrowing the error. Apps not - // deployed serverlessly will run into this cleanup function again in `res.end(), but it'll just no-op.) - await finishSentryProcessing(res); - - // We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it - // would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark - // the error as already having been captured.) - throw objectifiedErr; - } - }); - - // Since API route handlers are all async, nextjs always awaits the return value (meaning it's fine for us to return - // a promise here rather than a real result, and it saves us the overhead of an `await` call.) - return boundHandler(); - }; -}; - -type ResponseEndMethod = AugmentedNextApiResponse['end']; -type WrappedResponseEndMethod = AugmentedNextApiResponse['end']; - -/** - * Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish. - * - * Note: This wraps a sync method with an async method. While in general that's not a great idea in terms of keeping - * things in the right order, in this case it's safe, because the native `.end()` actually *is* async, and its run - * actually *is* awaited, just manually so (which reflects the fact that the core of the request/response code in Node - * by far predates the introduction of `async`/`await`). When `.end()` is done, it emits the `prefinish` event, and - * only once that fires does request processing continue. See - * https://github.com/nodejs/node/commit/7c9b607048f13741173d397795bac37707405ba7. - * - * @param origEnd The original `res.end()` method - * @returns The wrapped version - */ -function wrapEndMethod(origEnd: ResponseEndMethod): WrappedResponseEndMethod { - return async function newEnd(this: AugmentedNextApiResponse, ...args: unknown[]) { - await finishSentryProcessing(this); - - return origEnd.call(this, ...args); - }; -} - -/** - * Close the open transaction (if any) and flush events to Sentry. - * - * @param res The outgoing response for this request, on which the transaction is stored - */ -async function finishSentryProcessing(res: AugmentedNextApiResponse): Promise { - const { __sentryTransaction: transaction } = res; - - if (transaction) { - transaction.setHttpStatus(res.statusCode); - - // Push `transaction.finish` to the next event loop so open spans have a better chance of finishing before the - // transaction closes, and make sure to wait until that's done before flushing events - const transactionFinished: Promise = new Promise(resolve => { - setImmediate(() => { - transaction.finish(); - resolve(); - }); - }); - await transactionFinished; - } - - // Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda - // ends. If there was an error, rethrow it so that the normal exception-handling mechanisms can apply. - try { - __DEBUG_BUILD__ && logger.log('Flushing events...'); - await flush(2000); - __DEBUG_BUILD__ && logger.log('Done flushing events'); - } catch (e) { - __DEBUG_BUILD__ && logger.log('Error while flushing events:\n', e); - } -} diff --git a/packages/nextjs/test/utils/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts similarity index 99% rename from packages/nextjs/test/utils/withSentry.test.ts rename to packages/nextjs/test/config/withSentry.test.ts index ee68e17f9499..4a9b3908ae8c 100644 --- a/packages/nextjs/test/utils/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -4,7 +4,7 @@ import { Client, ClientOptions } from '@sentry/types'; import * as utils from '@sentry/utils'; import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'; -import { AugmentedNextApiResponse, withSentry, WrappedNextApiHandler } from '../../src/utils/withSentry'; +import { AugmentedNextApiResponse, withSentry, WrappedNextApiHandler } from '../../src/config/wrappers'; const FLUSH_DURATION = 200;