From a162d15c39393264520b8e966bf8851897ab8d12 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 10:09:51 +0000 Subject: [PATCH 01/10] Rename files --- .../nextjs/src/{server => common}/wrapApiHandlerWithSentry.ts | 0 .../src/{server => common}/wrapAppGetInitialPropsWithSentry.ts | 0 .../{server => common}/wrapDocumentGetInitialPropsWithSentry.ts | 0 .../src/{server => common}/wrapErrorGetInitialPropsWithSentry.ts | 0 .../src/{server => common}/wrapGetInitialPropsWithSentry.ts | 0 .../src/{server => common}/wrapGetServerSidePropsWithSentry.ts | 0 .../nextjs/src/{server => common}/wrapGetStaticPropsWithSentry.ts | 0 .../src/{server => common}/wrapServerComponentWithSentry.ts | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename packages/nextjs/src/{server => common}/wrapApiHandlerWithSentry.ts (100%) rename packages/nextjs/src/{server => common}/wrapAppGetInitialPropsWithSentry.ts (100%) rename packages/nextjs/src/{server => common}/wrapDocumentGetInitialPropsWithSentry.ts (100%) rename packages/nextjs/src/{server => common}/wrapErrorGetInitialPropsWithSentry.ts (100%) rename packages/nextjs/src/{server => common}/wrapGetInitialPropsWithSentry.ts (100%) rename packages/nextjs/src/{server => common}/wrapGetServerSidePropsWithSentry.ts (100%) rename packages/nextjs/src/{server => common}/wrapGetStaticPropsWithSentry.ts (100%) rename packages/nextjs/src/{server => common}/wrapServerComponentWithSentry.ts (100%) diff --git a/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapApiHandlerWithSentry.ts rename to packages/nextjs/src/common/wrapApiHandlerWithSentry.ts diff --git a/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts diff --git a/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts diff --git a/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts diff --git a/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts diff --git a/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts rename to packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts diff --git a/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts rename to packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts diff --git a/packages/nextjs/src/server/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts similarity index 100% rename from packages/nextjs/src/server/wrapServerComponentWithSentry.ts rename to packages/nextjs/src/common/wrapServerComponentWithSentry.ts From baa85a5b3c2514f3c0eaa22b74bdbeab9987ea17 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 10:32:19 +0000 Subject: [PATCH 02/10] I expect this to completely implode --- packages/nextjs/src/client/index.ts | 36 ------------- .../wrapAppGetInitialPropsWithSentry.ts | 20 -------- .../wrapDocumentGetInitialPropsWithSentry.ts | 22 -------- .../wrapErrorGetInitialPropsWithSentry.ts | 23 --------- .../client/wrapGetInitialPropsWithSentry.ts | 20 -------- .../wrapGetServerSidePropsWithSentry.ts | 18 ------- .../client/wrapGetStaticPropsWithSentry.ts | 20 -------- packages/nextjs/src/common/index.ts | 45 ++++++++++++++++ packages/nextjs/src/common/types.ts | 51 +++++++++++++++++++ .../utils/edgeWrapperUtils.ts | 5 +- .../src/{server => common}/utils/isBuild.ts | 0 .../utils/platformSupportsStreaming.ts | 0 .../{server => common}/utils/responseEnd.ts | 2 +- .../{server => common}/utils/wrapperUtils.ts | 0 .../src/common/wrapApiHandlerWithSentry.ts | 9 +++- .../wrapAppGetInitialPropsWithSentry.ts | 3 +- .../wrapDocumentGetInitialPropsWithSentry.ts | 3 +- .../wrapErrorGetInitialPropsWithSentry.ts | 3 +- .../common/wrapGetInitialPropsWithSentry.ts | 3 +- .../wrapGetServerSidePropsWithSentry.ts | 3 +- .../common/wrapGetStaticPropsWithSentry.ts | 3 +- .../wrapMiddlewareWithSentry.ts | 2 +- packages/nextjs/src/edge/index.ts | 50 +----------------- packages/nextjs/src/edge/utils/flush.ts | 20 -------- .../src/edge/wrapApiHandlerWithSentry.ts | 36 ------------- .../src/edge/wrapServerComponentWithSentry.ts | 37 -------------- packages/nextjs/src/index.client.ts | 1 + packages/nextjs/src/index.server.ts | 1 + packages/nextjs/src/index.types.ts | 4 +- packages/nextjs/src/server/index.ts | 48 +---------------- packages/nextjs/src/server/types.ts | 50 ------------------ 31 files changed, 119 insertions(+), 419 deletions(-) delete mode 100644 packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts delete mode 100644 packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts delete mode 100644 packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts delete mode 100644 packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts delete mode 100644 packages/nextjs/src/client/wrapGetServerSidePropsWithSentry.ts delete mode 100644 packages/nextjs/src/client/wrapGetStaticPropsWithSentry.ts create mode 100644 packages/nextjs/src/common/index.ts rename packages/nextjs/src/{edge => common}/utils/edgeWrapperUtils.ts (93%) rename packages/nextjs/src/{server => common}/utils/isBuild.ts (100%) rename packages/nextjs/src/{server => common}/utils/platformSupportsStreaming.ts (100%) rename packages/nextjs/src/{server => common}/utils/responseEnd.ts (98%) rename packages/nextjs/src/{server => common}/utils/wrapperUtils.ts (100%) rename packages/nextjs/src/{edge => common}/wrapMiddlewareWithSentry.ts (92%) delete mode 100644 packages/nextjs/src/edge/utils/flush.ts delete mode 100644 packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts delete mode 100644 packages/nextjs/src/edge/wrapServerComponentWithSentry.ts delete mode 100644 packages/nextjs/src/server/types.ts diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 665b557e4e7b..8c591cdb022f 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -129,39 +129,3 @@ function addClientIntegrations(options: BrowserOptions): void { export function withSentryConfig(exportedUserNextConfig: T): T { return exportedUserNextConfig; } - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideGetInitialProps, - wrapGetInitialPropsWithSentry, -} from './wrapGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideAppGetInitialProps, - wrapAppGetInitialPropsWithSentry, -} from './wrapAppGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideDocumentGetInitialProps, - wrapDocumentGetInitialPropsWithSentry, -} from './wrapDocumentGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideErrorGetInitialProps, - wrapErrorGetInitialPropsWithSentry, -} from './wrapErrorGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryGetServerSideProps, - wrapGetServerSidePropsWithSentry, -} from './wrapGetServerSidePropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryGetStaticProps, - wrapGetStaticPropsWithSentry, -} from './wrapGetStaticPropsWithSentry'; diff --git a/packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts deleted file mode 100644 index e5f8c40847ff..000000000000 --- a/packages/nextjs/src/client/wrapAppGetInitialPropsWithSentry.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type App from 'next/app'; - -type AppGetInitialProps = (typeof App)['getInitialProps']; - -/** - * A passthrough function in case this function is used on the clientside. We need to make the returned function async - * so we are consistent with the serverside implementation. - */ -export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetInitialProps): AppGetInitialProps { - return new Proxy(origAppGetInitialProps, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - return wrappingTarget.apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `wrapAppGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideAppGetInitialProps = wrapAppGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts deleted file mode 100644 index 20669a0af9f6..000000000000 --- a/packages/nextjs/src/client/wrapDocumentGetInitialPropsWithSentry.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type Document from 'next/document'; - -type DocumentGetInitialProps = typeof Document.getInitialProps; - -/** - * A passthrough function in case this function is used on the clientside. We need to make the returned function async - * so we are consistent with the serverside implementation. - */ -export function wrapDocumentGetInitialPropsWithSentry( - origDocumentGetInitialProps: DocumentGetInitialProps, -): DocumentGetInitialProps { - return new Proxy(origDocumentGetInitialProps, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - return wrappingTarget.apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `wrapDocumentGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideDocumentGetInitialProps = wrapDocumentGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts deleted file mode 100644 index ab32a2bf93cc..000000000000 --- a/packages/nextjs/src/client/wrapErrorGetInitialPropsWithSentry.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { NextPageContext } from 'next'; -import type { ErrorProps } from 'next/error'; - -type ErrorGetInitialProps = (context: NextPageContext) => Promise; - -/** - * A passthrough function in case this function is used on the clientside. We need to make the returned function async - * so we are consistent with the serverside implementation. - */ -export function wrapErrorGetInitialPropsWithSentry( - origErrorGetInitialProps: ErrorGetInitialProps, -): ErrorGetInitialProps { - return new Proxy(origErrorGetInitialProps, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - return wrappingTarget.apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `wrapErrorGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideErrorGetInitialProps = wrapErrorGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts deleted file mode 100644 index 37004f04bc6e..000000000000 --- a/packages/nextjs/src/client/wrapGetInitialPropsWithSentry.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { NextPage } from 'next'; - -type GetInitialProps = Required['getInitialProps']; - -/** - * A passthrough function in case this function is used on the clientside. We need to make the returned function async - * so we are consistent with the serverside implementation. - */ -export function wrapGetInitialPropsWithSentry(origGetInitialProps: GetInitialProps): GetInitialProps { - return new Proxy(origGetInitialProps, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - return wrappingTarget.apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `wrapGetInitialPropsWithSentry` instead. - */ -export const withSentryServerSideGetInitialProps = wrapGetInitialPropsWithSentry; diff --git a/packages/nextjs/src/client/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/client/wrapGetServerSidePropsWithSentry.ts deleted file mode 100644 index 50450c053a15..000000000000 --- a/packages/nextjs/src/client/wrapGetServerSidePropsWithSentry.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { GetServerSideProps } from 'next'; - -/** - * A passthrough function in case this function is used on the clientside. We need to make the returned function async - * so we are consistent with the serverside implementation. - */ -export function wrapGetServerSidePropsWithSentry(origGetServerSideProps: GetServerSideProps): GetServerSideProps { - return new Proxy(origGetServerSideProps, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - return wrappingTarget.apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `withSentryGetServerSideProps` instead. - */ -export const withSentryGetServerSideProps = wrapGetServerSidePropsWithSentry; diff --git a/packages/nextjs/src/client/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/client/wrapGetStaticPropsWithSentry.ts deleted file mode 100644 index 3b99737bcf20..000000000000 --- a/packages/nextjs/src/client/wrapGetStaticPropsWithSentry.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { GetStaticProps } from 'next'; - -type Props = { [key: string]: unknown }; - -/** - * A passthrough function in case this function is used on the clientside. We need to make the returned function async - * so we are consistent with the serverside implementation. - */ -export function wrapGetStaticPropsWithSentry(origGetStaticProps: GetStaticProps): GetStaticProps { - return new Proxy(origGetStaticProps, { - apply: (wrappingTarget, thisArg, args: Parameters>) => { - return wrappingTarget.apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `wrapGetStaticPropsWithSentry` instead. - */ -export const withSentryGetStaticProps = wrapGetStaticPropsWithSentry; diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts new file mode 100644 index 000000000000..88d8b38f2e87 --- /dev/null +++ b/packages/nextjs/src/common/index.ts @@ -0,0 +1,45 @@ +export { + // eslint-disable-next-line deprecation/deprecation + withSentryGetStaticProps, + wrapGetStaticPropsWithSentry, +} from './wrapGetStaticPropsWithSentry'; + +export { + // eslint-disable-next-line deprecation/deprecation + withSentryServerSideGetInitialProps, + wrapGetInitialPropsWithSentry, +} from './wrapGetInitialPropsWithSentry'; + +export { + // eslint-disable-next-line deprecation/deprecation + withSentryServerSideAppGetInitialProps, + wrapAppGetInitialPropsWithSentry, +} from './wrapAppGetInitialPropsWithSentry'; +export { + // eslint-disable-next-line deprecation/deprecation + withSentryServerSideDocumentGetInitialProps, + wrapDocumentGetInitialPropsWithSentry, +} from './wrapDocumentGetInitialPropsWithSentry'; +export { + // eslint-disable-next-line deprecation/deprecation + withSentryServerSideErrorGetInitialProps, + wrapErrorGetInitialPropsWithSentry, +} from './wrapErrorGetInitialPropsWithSentry'; + +export { + // eslint-disable-next-line deprecation/deprecation + withSentryGetServerSideProps, + wrapGetServerSidePropsWithSentry, +} from './wrapGetServerSidePropsWithSentry'; + +export { + // eslint-disable-next-line deprecation/deprecation + withSentry, + // eslint-disable-next-line deprecation/deprecation + withSentryAPI, + wrapApiHandlerWithSentry, +} from './wrapApiHandlerWithSentry'; + +export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; + +export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons'; diff --git a/packages/nextjs/src/common/types.ts b/packages/nextjs/src/common/types.ts index 6f1c9e5b2c4b..dc838b214276 100644 --- a/packages/nextjs/src/common/types.ts +++ b/packages/nextjs/src/common/types.ts @@ -1,3 +1,6 @@ +import type { Transaction, WrappedFunction } from '@sentry/types'; +import type { NextApiRequest, NextApiResponse } from 'next'; + export type ServerComponentContext = { componentRoute: string; componentType: string; @@ -6,3 +9,51 @@ export type ServerComponentContext = { }; export type VercelCronsConfig = { path?: string; schedule?: string }[] | undefined; + +// 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 = { + (req: NextApiRequest, res: NextApiResponse): void | Promise | unknown | Promise; + __sentry_route__?: string; + + /** + * A property we set in our integration tests to simulate running an API route on platforms that don't support streaming. + */ + __sentry_test_doesnt_support_streaming__?: true; +}; + +export type WrappedNextApiHandler = { + (req: NextApiRequest, res: NextApiResponse): Promise | Promise; + __sentry_route__?: string; + __sentry_wrapped__?: boolean; +}; + +export type AugmentedNextApiRequest = NextApiRequest & { + __withSentry_applied__?: boolean; +}; + +export type AugmentedNextApiResponse = NextApiResponse & { + __sentryTransaction?: Transaction; +}; + +export type ResponseEndMethod = AugmentedNextApiResponse['end']; +export type WrappedResponseEndMethod = AugmentedNextApiResponse['end'] & WrappedFunction; diff --git a/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts similarity index 93% rename from packages/nextjs/src/edge/utils/edgeWrapperUtils.ts rename to packages/nextjs/src/common/utils/edgeWrapperUtils.ts index 256499c97185..f3023665d106 100644 --- a/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts +++ b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts @@ -1,9 +1,8 @@ -import { captureException, getCurrentHub, hasTracingEnabled, startTransaction } from '@sentry/core'; +import { captureException, flush, getCurrentHub, hasTracingEnabled, startTransaction } from '@sentry/core'; import type { Span } from '@sentry/types'; import { addExceptionMechanism, logger, objectify, tracingContextFromHeaders } from '@sentry/utils'; -import type { EdgeRouteHandler } from '../types'; -import { flush } from './flush'; +import type { EdgeRouteHandler } from '../../edge/types'; /** * Wraps a function on the edge runtime with error and performance monitoring. diff --git a/packages/nextjs/src/server/utils/isBuild.ts b/packages/nextjs/src/common/utils/isBuild.ts similarity index 100% rename from packages/nextjs/src/server/utils/isBuild.ts rename to packages/nextjs/src/common/utils/isBuild.ts diff --git a/packages/nextjs/src/server/utils/platformSupportsStreaming.ts b/packages/nextjs/src/common/utils/platformSupportsStreaming.ts similarity index 100% rename from packages/nextjs/src/server/utils/platformSupportsStreaming.ts rename to packages/nextjs/src/common/utils/platformSupportsStreaming.ts diff --git a/packages/nextjs/src/server/utils/responseEnd.ts b/packages/nextjs/src/common/utils/responseEnd.ts similarity index 98% rename from packages/nextjs/src/server/utils/responseEnd.ts rename to packages/nextjs/src/common/utils/responseEnd.ts index ee2d9f803d3b..ffc216a72f2e 100644 --- a/packages/nextjs/src/server/utils/responseEnd.ts +++ b/packages/nextjs/src/common/utils/responseEnd.ts @@ -1,4 +1,4 @@ -import { flush } from '@sentry/node'; +import { flush } from '@sentry/core'; import type { Transaction } from '@sentry/types'; import { fill, logger } from '@sentry/utils'; import type { ServerResponse } from 'http'; diff --git a/packages/nextjs/src/server/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts similarity index 100% rename from packages/nextjs/src/server/utils/wrapperUtils.ts rename to packages/nextjs/src/common/utils/wrapperUtils.ts diff --git a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts index 524e29fa0ca0..b4af7d47893e 100644 --- a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts @@ -1,5 +1,10 @@ -import { getCurrentHub, hasTracingEnabled, runWithAsyncContext } from '@sentry/core'; -import { captureException, startTransaction } from '@sentry/node'; +import { + captureException, + getCurrentHub, + hasTracingEnabled, + runWithAsyncContext, + startTransaction, +} from '@sentry/core'; import type { Transaction } from '@sentry/types'; import { addExceptionMechanism, diff --git a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts index e1ecf50dad54..c39b903f717b 100644 --- a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts @@ -1,5 +1,4 @@ -import { hasTracingEnabled } from '@sentry/core'; -import { getCurrentHub } from '@sentry/node'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type App from 'next/app'; diff --git a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts index 1d821d86dcda..54b8c614f799 100644 --- a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts @@ -1,5 +1,4 @@ -import { hasTracingEnabled } from '@sentry/core'; -import { getCurrentHub } from '@sentry/node'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import type Document from 'next/document'; import { isBuild } from './utils/isBuild'; diff --git a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts index b99b04524d46..9ebf810e5ee0 100644 --- a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts @@ -1,5 +1,4 @@ -import { hasTracingEnabled } from '@sentry/core'; -import { getCurrentHub } from '@sentry/node'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { NextPageContext } from 'next'; import type { ErrorProps } from 'next/error'; diff --git a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts index d1613a80d2c9..601105bab7be 100644 --- a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts @@ -1,5 +1,4 @@ -import { hasTracingEnabled } from '@sentry/core'; -import { getCurrentHub } from '@sentry/node'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { NextPage } from 'next'; diff --git a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts index f37068bae206..f0a0517960fc 100644 --- a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts @@ -1,5 +1,4 @@ -import { hasTracingEnabled } from '@sentry/core'; -import { getCurrentHub } from '@sentry/node'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { GetServerSideProps } from 'next'; diff --git a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts index 78f910dfb0e4..e65d0ba8f90a 100644 --- a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts @@ -1,5 +1,4 @@ -import { hasTracingEnabled } from '@sentry/core'; -import { getCurrentHub } from '@sentry/node'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import type { GetStaticProps } from 'next'; import { isBuild } from './utils/isBuild'; diff --git a/packages/nextjs/src/edge/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts similarity index 92% rename from packages/nextjs/src/edge/wrapMiddlewareWithSentry.ts rename to packages/nextjs/src/common/wrapMiddlewareWithSentry.ts index 831a50eb8629..66cbbb046300 100644 --- a/packages/nextjs/src/edge/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts @@ -1,4 +1,4 @@ -import type { EdgeRouteHandler } from './types'; +import type { EdgeRouteHandler } from '../edge/types'; import { withEdgeWrapping } from './utils/edgeWrapperUtils'; /** diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 70cba9c356e6..404a6010628b 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -1,12 +1,6 @@ -import { getCurrentHub, getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; +import { getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; import type { Options } from '@sentry/types'; -import { - createStackParser, - GLOBAL_OBJ, - logger, - nodeStackLineParser, - stackParserFromStackParserOptions, -} from '@sentry/utils'; +import { createStackParser, GLOBAL_OBJ, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils'; import { getVercelEnv } from '../common/getVercelEnv'; import { setAsyncLocalStorageAsyncContextStrategy } from './asyncLocalStorageAsyncContextStrategy'; @@ -103,32 +97,6 @@ export function getSentryRelease(fallback?: string): string | undefined { ); } -/** - * Call `close()` on the current client, if there is one. See {@link Client.close}. - * - * @param timeout Maximum time in ms the client should wait to flush its event queue before shutting down. Omitting this - * parameter will cause the client to wait until all events are sent before disabling itself. - * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it - * doesn't (or if there's no client defined). - */ -export async function close(timeout?: number): Promise { - const client = getCurrentHub().getClient(); - if (client) { - return client.close(timeout); - } - __DEBUG_BUILD__ && logger.warn('Cannot flush events and disable SDK. No client defined.'); - return Promise.resolve(false); -} - -/** - * This is the getter for lastEventId. - * - * @returns The last event id of a captured event. - */ -export function lastEventId(): string | undefined { - return getCurrentHub().lastEventId(); -} - /** * Just a passthrough in case this is imported from the client. */ @@ -136,18 +104,4 @@ export function withSentryConfig(exportedUserNextConfig: T): T { return exportedUserNextConfig; } -export { flush } from './utils/flush'; - export * from '@sentry/core'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryAPI, - wrapApiHandlerWithSentry, -} from './wrapApiHandlerWithSentry'; - -export { wrapApiHandlerWithSentryVercelCrons } from '../common/wrapApiHandlerWithSentryVercelCrons'; - -export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry'; - -export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; diff --git a/packages/nextjs/src/edge/utils/flush.ts b/packages/nextjs/src/edge/utils/flush.ts deleted file mode 100644 index 5daa52936391..000000000000 --- a/packages/nextjs/src/edge/utils/flush.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; -import type { Client } from '@sentry/types'; -import { logger } from '@sentry/utils'; - -/** - * Call `flush()` on the current client, if there is one. See {@link Client.flush}. - * - * @param timeout Maximum time in ms the client should wait to flush its event queue. Omitting this parameter will cause - * the client to wait until all events are sent before resolving the promise. - * @returns A promise which resolves to `true` if the queue successfully drains before the timeout, or `false` if it - * doesn't (or if there's no client defined). - */ -export async function flush(timeout?: number): Promise { - const client = getCurrentHub().getClient(); - if (client) { - return client.flush(timeout); - } - __DEBUG_BUILD__ && logger.warn('Cannot flush events. No client defined.'); - return Promise.resolve(false); -} diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts deleted file mode 100644 index f903d77f46c4..000000000000 --- a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; - -import type { EdgeRouteHandler } from './types'; -import { withEdgeWrapping } from './utils/edgeWrapperUtils'; - -/** - * Wraps a Next.js edge route handler with Sentry error and performance instrumentation. - */ -export function wrapApiHandlerWithSentry( - handler: H, - parameterizedRoute: string, -): (...params: Parameters) => Promise> { - return new Proxy(handler, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - const req = args[0]; - - const activeSpan = getCurrentHub().getScope().getSpan(); - - const wrappedHandler = withEdgeWrapping(wrappingTarget, { - spanDescription: - activeSpan || !(req instanceof Request) - ? `handler (${parameterizedRoute})` - : `${req.method} ${parameterizedRoute}`, - spanOp: activeSpan ? 'function' : 'http.server', - mechanismFunctionName: 'wrapApiHandlerWithSentry', - }); - - return wrappedHandler.apply(thisArg, args); - }, - }); -} - -/** - * @deprecated Use `wrapApiHandlerWithSentry` instead. - */ -export const withSentryAPI = wrapApiHandlerWithSentry; diff --git a/packages/nextjs/src/edge/wrapServerComponentWithSentry.ts b/packages/nextjs/src/edge/wrapServerComponentWithSentry.ts deleted file mode 100644 index 349207e7b039..000000000000 --- a/packages/nextjs/src/edge/wrapServerComponentWithSentry.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { captureException } from '@sentry/core'; - -import type { ServerComponentContext } from '../common/types'; - -/** - * Wraps an `app` directory server component with Sentry error instrumentation. - */ -export function wrapServerComponentWithSentry any>( - appDirComponent: F, - _context: ServerComponentContext, -): F { - // 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) => { - let maybePromiseResult; - - try { - maybePromiseResult = originalFunction.apply(thisArg, args); - } catch (e) { - captureException(e); - throw e; - } - - if (typeof maybePromiseResult === 'object' && maybePromiseResult !== null && 'then' in maybePromiseResult) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return maybePromiseResult.then(null, (e: Error) => { - captureException(e); - throw e; - }); - } else { - return maybePromiseResult; - } - }, - }); -} diff --git a/packages/nextjs/src/index.client.ts b/packages/nextjs/src/index.client.ts index c7ae01e45598..ef4c7e52af6b 100644 --- a/packages/nextjs/src/index.client.ts +++ b/packages/nextjs/src/index.client.ts @@ -1,3 +1,4 @@ +export * from './common'; export * from './client'; // This file is the main entrypoint for non-Next.js build pipelines that use diff --git a/packages/nextjs/src/index.server.ts b/packages/nextjs/src/index.server.ts index 4a9536d25ae1..97bf4fbd31f4 100644 --- a/packages/nextjs/src/index.server.ts +++ b/packages/nextjs/src/index.server.ts @@ -1,3 +1,4 @@ +export * from './common'; export * from './config'; export * from './server'; diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 6fcf7436399b..1bdabaa973e6 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -2,6 +2,7 @@ // We export everything from both the client part of the SDK and from the server part. Some of the exports collide, // which is not allowed, unless we redifine the colliding exports in this file - which we do below. +export * from './common'; export * from './config'; export * from './client'; export * from './server'; @@ -27,9 +28,6 @@ export declare const Integrations: typeof clientSdk.Integrations & export declare const defaultIntegrations: Integration[]; export declare const defaultStackParser: StackParser; -export declare function close(timeout?: number | undefined): PromiseLike; -export declare function flush(timeout?: number | undefined): PromiseLike; -export declare function lastEventId(): string | undefined; export declare function getSentryRelease(fallback?: string): string | undefined; export declare const ErrorBoundary: typeof clientSdk.ErrorBoundary; diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index d92e406cda3f..7c7ece5061f6 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; import { getVercelEnv } from '../common/getVercelEnv'; import { buildMetadata } from '../common/metadata'; -import { isBuild } from './utils/isBuild'; +import { isBuild } from '../common/utils/isBuild'; export * from '@sentry/node'; export { captureUnderscoreErrorException } from '../common/_error'; @@ -156,49 +156,3 @@ function addServerIntegrations(options: NodeOptions): void { const deprecatedIsBuild = (): boolean => isBuild(); // eslint-disable-next-line deprecation/deprecation export { deprecatedIsBuild as isBuild }; - -export { wrapApiHandlerWithSentryVercelCrons } from '../common/wrapApiHandlerWithSentryVercelCrons'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryGetStaticProps, - wrapGetStaticPropsWithSentry, -} from './wrapGetStaticPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideGetInitialProps, - wrapGetInitialPropsWithSentry, -} from './wrapGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideAppGetInitialProps, - wrapAppGetInitialPropsWithSentry, -} from './wrapAppGetInitialPropsWithSentry'; -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideDocumentGetInitialProps, - wrapDocumentGetInitialPropsWithSentry, -} from './wrapDocumentGetInitialPropsWithSentry'; -export { - // eslint-disable-next-line deprecation/deprecation - withSentryServerSideErrorGetInitialProps, - wrapErrorGetInitialPropsWithSentry, -} from './wrapErrorGetInitialPropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentryGetServerSideProps, - wrapGetServerSidePropsWithSentry, -} from './wrapGetServerSidePropsWithSentry'; - -export { - // eslint-disable-next-line deprecation/deprecation - withSentry, - // eslint-disable-next-line deprecation/deprecation - withSentryAPI, - wrapApiHandlerWithSentry, -} from './wrapApiHandlerWithSentry'; - -export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; diff --git a/packages/nextjs/src/server/types.ts b/packages/nextjs/src/server/types.ts deleted file mode 100644 index a411c4ea62cf..000000000000 --- a/packages/nextjs/src/server/types.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Transaction, WrappedFunction } 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 = { - (req: NextApiRequest, res: NextApiResponse): void | Promise | unknown | Promise; - __sentry_route__?: string; - - /** - * A property we set in our integration tests to simulate running an API route on platforms that don't support streaming. - */ - __sentry_test_doesnt_support_streaming__?: true; -}; - -export type WrappedNextApiHandler = { - (req: NextApiRequest, res: NextApiResponse): Promise | Promise; - __sentry_route__?: string; - __sentry_wrapped__?: boolean; -}; - -export type AugmentedNextApiRequest = NextApiRequest & { - __withSentry_applied__?: boolean; -}; - -export type AugmentedNextApiResponse = NextApiResponse & { - __sentryTransaction?: Transaction; -}; - -export type ResponseEndMethod = AugmentedNextApiResponse['end']; -export type WrappedResponseEndMethod = AugmentedNextApiResponse['end'] & WrappedFunction; From 92715502ca14fcac09ff80f1518538cb11d793bc Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 11:11:36 +0000 Subject: [PATCH 03/10] . --- packages/nextjs/src/common/types.ts | 2 +- packages/nextjs/test/config/withSentry.test.ts | 4 ++-- packages/nextjs/test/config/wrappers.test.ts | 2 +- packages/nextjs/test/edge/withSentryAPI.test.ts | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/nextjs/src/common/types.ts b/packages/nextjs/src/common/types.ts index dc838b214276..ca4faf92abbb 100644 --- a/packages/nextjs/src/common/types.ts +++ b/packages/nextjs/src/common/types.ts @@ -32,7 +32,7 @@ export type VercelCronsConfig = { path?: string; schedule?: string }[] | undefin // wrapped route from being wrapped again by the auto-wrapper. export type NextApiHandler = { - (req: NextApiRequest, res: NextApiResponse): void | Promise | unknown | Promise; + (req?: NextApiRequest, res?: NextApiResponse): void | Promise | unknown | Promise; __sentry_route__?: string; /** diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index 92315374836b..8e9a83365944 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -4,8 +4,8 @@ import * as Sentry from '@sentry/node'; import type { Client, ClientOptions } from '@sentry/types'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { withSentry } from '../../src/server'; -import type { AugmentedNextApiResponse, NextApiHandler } from '../../src/server/types'; +import { withSentry } from '../../src/common'; +import type { AugmentedNextApiResponse, NextApiHandler } from '../../src/common/types'; // The wrap* functions require the hub to have tracing extensions. This is normally called by the NodeClient // constructor but the client isn't used in these tests. diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index 9195154991be..e926cd3c62dc 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -3,7 +3,7 @@ import { addTracingExtensions } from '@sentry/core'; import * as SentryNode from '@sentry/node'; import type { IncomingMessage, ServerResponse } from 'http'; -import { wrapGetInitialPropsWithSentry, wrapGetServerSidePropsWithSentry } from '../../src/server'; +import { wrapGetInitialPropsWithSentry, wrapGetServerSidePropsWithSentry } from '../../src/common'; const startTransactionSpy = jest.spyOn(SentryCore, 'startTransaction'); const originalGetCurrentHub = jest.requireActual('@sentry/node').getCurrentHub; diff --git a/packages/nextjs/test/edge/withSentryAPI.test.ts b/packages/nextjs/test/edge/withSentryAPI.test.ts index a991ecf88e6b..2a64866b8a6f 100644 --- a/packages/nextjs/test/edge/withSentryAPI.test.ts +++ b/packages/nextjs/test/edge/withSentryAPI.test.ts @@ -1,6 +1,6 @@ import * as coreSdk from '@sentry/core'; -import { wrapApiHandlerWithSentry } from '../../src/edge'; +import { wrapApiHandlerWithSentry } from '../../src/common'; // The wrap* functions require the hub to have tracing extensions. This is normally called by the EdgeClient // constructor but the client isn't used in these tests. @@ -48,6 +48,7 @@ describe('wrapApiHandlerWithSentry', () => { const wrappedFunction = wrapApiHandlerWithSentry(origFunction, '/user/[userId]/post/[postId]'); const request = new Request('https://sentry.io/'); + // @ts-ignore missing fields await wrappedFunction(request); expect(startTransactionSpy).toHaveBeenCalledTimes(1); expect(startTransactionSpy).toHaveBeenCalledWith( From 7f86b558b08908acc978dd5032ac8bdbe28497c2 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 11:53:03 +0000 Subject: [PATCH 04/10] we have to keep the edge api handler :( --- packages/nextjs/src/common/types.ts | 2 +- packages/nextjs/src/edge/index.ts | 6 ++++ .../src/edge/wrapApiHandlerWithSentry.ts | 36 +++++++++++++++++++ .../nextjs/test/config/withSentry.test.ts | 7 ++-- packages/nextjs/test/config/wrappers.test.ts | 3 +- .../nextjs/test/edge/edgeWrapperUtils.test.ts | 2 +- .../nextjs/test/edge/withSentryAPI.test.ts | 3 +- 7 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts diff --git a/packages/nextjs/src/common/types.ts b/packages/nextjs/src/common/types.ts index ca4faf92abbb..dc838b214276 100644 --- a/packages/nextjs/src/common/types.ts +++ b/packages/nextjs/src/common/types.ts @@ -32,7 +32,7 @@ export type VercelCronsConfig = { path?: string; schedule?: string }[] | undefin // wrapped route from being wrapped again by the auto-wrapper. export type NextApiHandler = { - (req?: NextApiRequest, res?: NextApiResponse): void | Promise | unknown | Promise; + (req: NextApiRequest, res: NextApiResponse): void | Promise | unknown | Promise; __sentry_route__?: string; /** diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 404a6010628b..580f9c1ffb69 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -105,3 +105,9 @@ export function withSentryConfig(exportedUserNextConfig: T): T { } export * from '@sentry/core'; + +export { + // eslint-disable-next-line deprecation/deprecation + withSentryAPI, + wrapApiHandlerWithSentry, +} from './wrapApiHandlerWithSentry'; diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts new file mode 100644 index 000000000000..46691b3cdce5 --- /dev/null +++ b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts @@ -0,0 +1,36 @@ +import { getCurrentHub } from '@sentry/core'; + +import { withEdgeWrapping } from '../common/utils/edgeWrapperUtils'; +import type { EdgeRouteHandler } from './types'; + +/** + * Wraps a Next.js edge route handler with Sentry error and performance instrumentation. + */ +export function wrapApiHandlerWithSentry( + handler: H, + parameterizedRoute: string, +): (...params: Parameters) => Promise> { + return new Proxy(handler, { + apply: (wrappingTarget, thisArg, args: Parameters) => { + const req = args[0]; + + const activeSpan = getCurrentHub().getScope().getSpan(); + + const wrappedHandler = withEdgeWrapping(wrappingTarget, { + spanDescription: + activeSpan || !(req instanceof Request) + ? `handler (${parameterizedRoute})` + : `${req.method} ${parameterizedRoute}`, + spanOp: activeSpan ? 'function' : 'http.server', + mechanismFunctionName: 'wrapApiHandlerWithSentry', + }); + + return wrappedHandler.apply(thisArg, args); + }, + }); +} + +/** + * @deprecated Use `wrapApiHandlerWithSentry` instead. + */ +export const withSentryAPI = wrapApiHandlerWithSentry; diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index 8e9a83365944..cca5a1cd3250 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -1,6 +1,5 @@ -import * as hub from '@sentry/core'; +import * as SentryCore from '@sentry/core'; import { addTracingExtensions } from '@sentry/core'; -import * as Sentry from '@sentry/node'; import type { Client, ClientOptions } from '@sentry/types'; import type { NextApiRequest, NextApiResponse } from 'next'; @@ -41,7 +40,7 @@ async function callWrappedHandler(wrappedHandler: NextApiHandler, req: NextApiRe } } -const startTransactionSpy = jest.spyOn(Sentry, 'startTransaction'); +const startTransactionSpy = jest.spyOn(SentryCore, 'startTransaction'); describe('withSentry', () => { let req: NextApiRequest, res: NextApiResponse; @@ -71,7 +70,7 @@ describe('withSentry', () => { describe('tracing', () => { it('starts a transaction and sets metadata when tracing is enabled', async () => { - jest.spyOn(hub.Hub.prototype, 'getClient').mockReturnValueOnce({ + jest.spyOn(SentryCore.Hub.prototype, 'getClient').mockReturnValueOnce({ getOptions: () => ({ tracesSampleRate: 1, instrumenter: 'sentry' } as ClientOptions), } as Client); diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index e926cd3c62dc..44528ec17cc1 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -1,6 +1,5 @@ import * as SentryCore from '@sentry/core'; import { addTracingExtensions } from '@sentry/core'; -import * as SentryNode from '@sentry/node'; import type { IncomingMessage, ServerResponse } from 'http'; import { wrapGetInitialPropsWithSentry, wrapGetServerSidePropsWithSentry } from '../../src/common'; @@ -23,7 +22,7 @@ describe('data-fetching function wrappers', () => { res = { end: jest.fn() } as unknown as ServerResponse; jest.spyOn(SentryCore, 'hasTracingEnabled').mockReturnValue(true); - jest.spyOn(SentryNode, 'getCurrentHub').mockImplementation(() => { + jest.spyOn(SentryCore, 'getCurrentHub').mockImplementation(() => { const hub = originalGetCurrentHub(); hub.getClient = () => diff --git a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts index cdc7cc4986e2..3dd963077a00 100644 --- a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts +++ b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts @@ -1,7 +1,7 @@ import * as coreSdk from '@sentry/core'; import { addTracingExtensions } from '@sentry/core'; -import { withEdgeWrapping } from '../../src/edge/utils/edgeWrapperUtils'; +import { withEdgeWrapping } from '../../src/common/utils/edgeWrapperUtils'; // The wrap* functions require the hub to have tracing extensions. This is normally called by the EdgeClient // constructor but the client isn't used in these tests. diff --git a/packages/nextjs/test/edge/withSentryAPI.test.ts b/packages/nextjs/test/edge/withSentryAPI.test.ts index 2a64866b8a6f..a991ecf88e6b 100644 --- a/packages/nextjs/test/edge/withSentryAPI.test.ts +++ b/packages/nextjs/test/edge/withSentryAPI.test.ts @@ -1,6 +1,6 @@ import * as coreSdk from '@sentry/core'; -import { wrapApiHandlerWithSentry } from '../../src/common'; +import { wrapApiHandlerWithSentry } from '../../src/edge'; // The wrap* functions require the hub to have tracing extensions. This is normally called by the EdgeClient // constructor but the client isn't used in these tests. @@ -48,7 +48,6 @@ describe('wrapApiHandlerWithSentry', () => { const wrappedFunction = wrapApiHandlerWithSentry(origFunction, '/user/[userId]/post/[postId]'); const request = new Request('https://sentry.io/'); - // @ts-ignore missing fields await wrappedFunction(request); expect(startTransactionSpy).toHaveBeenCalledTimes(1); expect(startTransactionSpy).toHaveBeenCalledWith( From cf0733ffc964ae9202c583f6b1f4be9a9890a882 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 12:20:55 +0000 Subject: [PATCH 05/10] fix lint --- packages/nextjs/src/config/templates/apiWrapperTemplate.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/nextjs/src/config/templates/apiWrapperTemplate.ts b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts index 0eccf3024a76..28d5e4efc806 100644 --- a/packages/nextjs/src/config/templates/apiWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/apiWrapperTemplate.ts @@ -13,10 +13,7 @@ import * as origModule from '__SENTRY_WRAPPING_TARGET_FILE__'; import * as Sentry from '@sentry/nextjs'; import type { PageConfig } from 'next'; -import type { VercelCronsConfig } from '../../common/types'; -// 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 '../../server/types'; +import type { NextApiHandler, VercelCronsConfig } from '../../common/types'; type NextApiModule = ( | { From f07c13cde6bdf2b41cff60904fc5d409c0607c1d Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 12:29:43 +0000 Subject: [PATCH 06/10] Export n stuff --- packages/nextjs/src/client/index.ts | 2 ++ packages/nextjs/src/common/index.ts | 8 -------- packages/nextjs/src/edge/index.ts | 6 +++++- packages/nextjs/src/index.client.ts | 1 - packages/nextjs/src/index.server.ts | 1 - packages/nextjs/src/index.types.ts | 1 - packages/nextjs/src/server/index.ts | 10 ++++++++++ 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 8c591cdb022f..0adda732916b 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -129,3 +129,5 @@ function addClientIntegrations(options: BrowserOptions): void { export function withSentryConfig(exportedUserNextConfig: T): T { return exportedUserNextConfig; } + +export * from '../common'; diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts index 88d8b38f2e87..1982933910e4 100644 --- a/packages/nextjs/src/common/index.ts +++ b/packages/nextjs/src/common/index.ts @@ -32,14 +32,6 @@ export { wrapGetServerSidePropsWithSentry, } from './wrapGetServerSidePropsWithSentry'; -export { - // eslint-disable-next-line deprecation/deprecation - withSentry, - // eslint-disable-next-line deprecation/deprecation - withSentryAPI, - wrapApiHandlerWithSentry, -} from './wrapApiHandlerWithSentry'; - export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons'; diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 580f9c1ffb69..6c2967d30f9b 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -106,8 +106,12 @@ export function withSentryConfig(exportedUserNextConfig: T): T { export * from '@sentry/core'; +// eslint-disable-next-line import/export +export * from '../common'; + export { - // eslint-disable-next-line deprecation/deprecation + // eslint-disable-next-line deprecation/deprecation, import/export withSentryAPI, + // eslint-disable-next-line import/export wrapApiHandlerWithSentry, } from './wrapApiHandlerWithSentry'; diff --git a/packages/nextjs/src/index.client.ts b/packages/nextjs/src/index.client.ts index ef4c7e52af6b..c7ae01e45598 100644 --- a/packages/nextjs/src/index.client.ts +++ b/packages/nextjs/src/index.client.ts @@ -1,4 +1,3 @@ -export * from './common'; export * from './client'; // This file is the main entrypoint for non-Next.js build pipelines that use diff --git a/packages/nextjs/src/index.server.ts b/packages/nextjs/src/index.server.ts index 97bf4fbd31f4..4a9536d25ae1 100644 --- a/packages/nextjs/src/index.server.ts +++ b/packages/nextjs/src/index.server.ts @@ -1,4 +1,3 @@ -export * from './common'; export * from './config'; export * from './server'; diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 1bdabaa973e6..5fbcf683ec38 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -2,7 +2,6 @@ // We export everything from both the client part of the SDK and from the server part. Some of the exports collide, // which is not allowed, unless we redifine the colliding exports in this file - which we do below. -export * from './common'; export * from './config'; export * from './client'; export * from './server'; diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 7c7ece5061f6..0e43291d019d 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -156,3 +156,13 @@ function addServerIntegrations(options: NodeOptions): void { const deprecatedIsBuild = (): boolean => isBuild(); // eslint-disable-next-line deprecation/deprecation export { deprecatedIsBuild as isBuild }; + +export * from '../common'; + +export { + // eslint-disable-next-line deprecation/deprecation + withSentry, + // eslint-disable-next-line deprecation/deprecation + withSentryAPI, + wrapApiHandlerWithSentry, +} from '../common/wrapApiHandlerWithSentry'; From 9420176c0e5e18b13bd654cc9837e921e998a3f0 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 14:43:57 +0000 Subject: [PATCH 07/10] wrong export --- packages/nextjs/test/config/withSentry.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index cca5a1cd3250..c7862e1473df 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -3,8 +3,8 @@ import { addTracingExtensions } from '@sentry/core'; import type { Client, ClientOptions } from '@sentry/types'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { withSentry } from '../../src/common'; import type { AugmentedNextApiResponse, NextApiHandler } from '../../src/common/types'; +import { withSentry } from '../../src/server'; // The wrap* functions require the hub to have tracing extensions. This is normally called by the NodeClient // constructor but the client isn't used in these tests. From cfd278fd881e7893eb0cbb1a08634783e8b6bf9f Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Mon, 7 Aug 2023 15:36:55 +0000 Subject: [PATCH 08/10] export middleware --- packages/nextjs/src/common/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts index 1982933910e4..69b37d181cfa 100644 --- a/packages/nextjs/src/common/index.ts +++ b/packages/nextjs/src/common/index.ts @@ -35,3 +35,5 @@ export { export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons'; + +export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry'; From 4b26d218dda3aebda199162cde9367994c1c43ff Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 8 Aug 2023 10:13:30 +0000 Subject: [PATCH 09/10] Remove logic that should never ever happen --- packages/nextjs/src/common/utils/responseEnd.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/nextjs/src/common/utils/responseEnd.ts b/packages/nextjs/src/common/utils/responseEnd.ts index ffc216a72f2e..4cc31de89fb5 100644 --- a/packages/nextjs/src/common/utils/responseEnd.ts +++ b/packages/nextjs/src/common/utils/responseEnd.ts @@ -41,18 +41,7 @@ export function autoEndTransactionOnResponseEnd(transaction: Transaction, res: S export async function finishTransaction(transaction: Transaction | undefined, res: ServerResponse): Promise { if (transaction) { transaction.setHttpStatus(res.statusCode); - - // If any open spans are set to finish when the response ends, it sets up a race condition between their `finish` - // calls and the transaction's `finish` call - and any spans which lose the race will get dropped from the - // transaction. To prevent this, push `transaction.finish` to the next event loop so that it's guaranteed to lose - // the race, and wait for it to be done before flushing events. - const transactionFinished: Promise = new Promise(resolve => { - setImmediate(() => { - transaction.finish(); - resolve(); - }); - }); - await transactionFinished; + transaction.finish(); } } From 6a08aae30e63b32e98039d9d99698fbf3c005766 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 8 Aug 2023 10:15:33 +0000 Subject: [PATCH 10/10] newlines --- packages/nextjs/src/common/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts index 69b37d181cfa..ccd4a628634e 100644 --- a/packages/nextjs/src/common/index.ts +++ b/packages/nextjs/src/common/index.ts @@ -15,11 +15,13 @@ export { withSentryServerSideAppGetInitialProps, wrapAppGetInitialPropsWithSentry, } from './wrapAppGetInitialPropsWithSentry'; + export { // eslint-disable-next-line deprecation/deprecation withSentryServerSideDocumentGetInitialProps, wrapDocumentGetInitialPropsWithSentry, } from './wrapDocumentGetInitialPropsWithSentry'; + export { // eslint-disable-next-line deprecation/deprecation withSentryServerSideErrorGetInitialProps,