From 043ae7eb916ae4138a6dd8140cc81b6b25885e4e Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Tue, 13 Aug 2024 13:42:35 +0200 Subject: [PATCH] feat(nextjs): Always transmit trace data to the client (#13337) --- .../nextjs/src/common/utils/wrapperUtils.ts | 15 +++++-- .../wrapAppGetInitialPropsWithSentry.ts | 45 +++++++++---------- .../wrapDocumentGetInitialPropsWithSentry.ts | 5 ++- .../wrapErrorGetInitialPropsWithSentry.ts | 42 ++++++++--------- .../common/wrapGetInitialPropsWithSentry.ts | 42 ++++++++--------- .../wrapGetServerSidePropsWithSentry.ts | 36 ++++++--------- .../common/wrapGetStaticPropsWithSentry.ts | 4 +- 7 files changed, 89 insertions(+), 100 deletions(-) diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index 306bc96e30f6..f07970e4db3b 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -6,6 +6,7 @@ import { SPAN_STATUS_OK, captureException, continueTrace, + getTraceData, startInactiveSpan, startSpan, startSpanManual, @@ -88,8 +89,11 @@ export function withTracedServerSideDataFetcher Pr /** Name of the data fetching method - will be used for describing the data fetcher's span. */ dataFetchingMethodName: string; }, -): (...params: Parameters) => Promise> { - return async function (this: unknown, ...args: Parameters): Promise> { +): (...params: Parameters) => Promise<{ data: ReturnType; sentryTrace?: string; baggage?: string }> { + return async function ( + this: unknown, + ...args: Parameters + ): Promise<{ data: ReturnType; sentryTrace?: string; baggage?: string }> { return escapeNextjsTracing(() => { const isolationScope = commonObjectToIsolationScope(req); return withIsolationScope(isolationScope, () => { @@ -116,8 +120,13 @@ export function withTracedServerSideDataFetcher Pr }, async dataFetcherSpan => { dataFetcherSpan.setStatus({ code: SPAN_STATUS_OK }); + const { 'sentry-trace': sentryTrace, baggage } = getTraceData(); try { - return await origDataFetcher.apply(this, args); + return { + sentryTrace: sentryTrace, + baggage: baggage, + data: await origDataFetcher.apply(this, args), + }; } catch (e) { dataFetcherSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); requestSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); diff --git a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts index 7bd04342c0ab..2c7b0adc7d7b 100644 --- a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts @@ -1,9 +1,7 @@ -import { getActiveSpan, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core'; -import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type App from 'next/app'; import { isBuild } from './utils/isBuild'; -import { getSpanFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; type AppGetInitialProps = (typeof App)['getInitialProps']; @@ -26,6 +24,7 @@ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetI const { req, res } = context.ctx; const errorWrappedAppGetInitialProps = withErrorInstrumentation(wrappingTarget); + // Generally we can assume that `req` and `res` are always defined on the server: // https://nextjs.org/docs/api-reference/data-fetching/get-initial-props#context-object // This does not seem to be the case in dev mode. Because we have no clean way of associating the the data fetcher @@ -37,16 +36,21 @@ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetI dataFetchingMethodName: 'getInitialProps', }); - const appGetInitialProps: { - pageProps: { - _sentryTraceData?: string; - _sentryBaggage?: string; + const { + data: appGetInitialProps, + sentryTrace, + baggage, + }: { + data: { + pageProps: { + _sentryTraceData?: string; + _sentryBaggage?: string; + }; }; + sentryTrace?: string; + baggage?: string; } = await tracedGetInitialProps.apply(thisArg, args); - const activeSpan = getActiveSpan(); - const requestSpan = getSpanFromRequest(req) ?? (activeSpan ? getRootSpan(activeSpan) : undefined); - // Per definition, `pageProps` is not optional, however an increased amount of users doesn't seem to call // `App.getInitialProps(appContext)` in their custom `_app` pages which is required as per // https://nextjs.org/docs/advanced-features/custom-app - resulting in missing `pageProps`. @@ -55,21 +59,14 @@ export function wrapAppGetInitialPropsWithSentry(origAppGetInitialProps: AppGetI appGetInitialProps.pageProps = {}; } - if (requestSpan) { - const sentryTrace = spanToTraceHeader(requestSpan); - - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (sentryTrace) { - appGetInitialProps.pageProps._sentryTraceData = sentryTrace; - } - - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(requestSpan); - const baggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (sentryTrace) { + appGetInitialProps.pageProps._sentryTraceData = sentryTrace; + } - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (baggage) { - appGetInitialProps.pageProps._sentryBaggage = baggage; - } + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (baggage) { + appGetInitialProps.pageProps._sentryBaggage = baggage; } return appGetInitialProps; diff --git a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts index 162081accb05..192e70f093b1 100644 --- a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts @@ -17,7 +17,7 @@ export function wrapDocumentGetInitialPropsWithSentry( origDocumentGetInitialProps: DocumentGetInitialProps, ): DocumentGetInitialProps { return new Proxy(origDocumentGetInitialProps, { - apply: (wrappingTarget, thisArg, args: Parameters) => { + apply: async (wrappingTarget, thisArg, args: Parameters) => { if (isBuild()) { return wrappingTarget.apply(thisArg, args); } @@ -37,7 +37,8 @@ export function wrapDocumentGetInitialPropsWithSentry( dataFetchingMethodName: 'getInitialProps', }); - return tracedGetInitialProps.apply(thisArg, args); + const { data } = await tracedGetInitialProps.apply(thisArg, args); + return data; } else { return errorWrappedGetInitialProps.apply(thisArg, args); } diff --git a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts index 347283494b38..a2bd559342a4 100644 --- a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts @@ -1,10 +1,8 @@ -import { getActiveSpan, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core'; -import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { NextPageContext } from 'next'; import type { ErrorProps } from 'next/error'; import { isBuild } from './utils/isBuild'; -import { getSpanFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; type ErrorGetInitialProps = (context: NextPageContext) => Promise; @@ -40,29 +38,27 @@ export function wrapErrorGetInitialPropsWithSentry( dataFetchingMethodName: 'getInitialProps', }); - const errorGetInitialProps: ErrorProps & { - _sentryTraceData?: string; - _sentryBaggage?: string; + const { + data: errorGetInitialProps, + baggage, + sentryTrace, + }: { + data: ErrorProps & { + _sentryTraceData?: string; + _sentryBaggage?: string; + }; + baggage?: string; + sentryTrace?: string; } = await tracedGetInitialProps.apply(thisArg, args); - const activeSpan = getActiveSpan(); - const requestSpan = getSpanFromRequest(req) ?? (activeSpan ? getRootSpan(activeSpan) : undefined); - - if (requestSpan) { - const sentryTrace = spanToTraceHeader(requestSpan); - - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (sentryTrace) { - errorGetInitialProps._sentryTraceData = sentryTrace; - } - - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(requestSpan); - const baggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (sentryTrace) { + errorGetInitialProps._sentryTraceData = sentryTrace; + } - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (baggage) { - errorGetInitialProps._sentryBaggage = baggage; - } + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (baggage) { + errorGetInitialProps._sentryBaggage = baggage; } return errorGetInitialProps; diff --git a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts index 1a1e351f83ed..2624aefb4d24 100644 --- a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts @@ -1,9 +1,7 @@ -import { getActiveSpan, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core'; -import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { NextPage } from 'next'; import { isBuild } from './utils/isBuild'; -import { getSpanFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; type GetInitialProps = Required['getInitialProps']; @@ -36,29 +34,27 @@ export function wrapGetInitialPropsWithSentry(origGetInitialProps: GetInitialPro dataFetchingMethodName: 'getInitialProps', }); - const initialProps: { - _sentryTraceData?: string; - _sentryBaggage?: string; + const { + data: initialProps, + baggage, + sentryTrace, + }: { + data: { + _sentryTraceData?: string; + _sentryBaggage?: string; + }; + baggage?: string; + sentryTrace?: string; } = (await tracedGetInitialProps.apply(thisArg, args)) ?? {}; // Next.js allows undefined to be returned from a getInitialPropsFunction. - const activeSpan = getActiveSpan(); - const requestSpan = getSpanFromRequest(req) ?? (activeSpan ? getRootSpan(activeSpan) : undefined); - - if (requestSpan) { - const sentryTrace = spanToTraceHeader(requestSpan); - - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (sentryTrace) { - initialProps._sentryTraceData = sentryTrace; - } - - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(requestSpan); - const baggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (sentryTrace) { + initialProps._sentryTraceData = sentryTrace; + } - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (baggage) { - initialProps._sentryBaggage = baggage; - } + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (baggage) { + initialProps._sentryBaggage = baggage; } return initialProps; diff --git a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts index 1be908042b5e..0037bad36300 100644 --- a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts @@ -1,9 +1,7 @@ -import { getActiveSpan, getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core'; -import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { GetServerSideProps } from 'next'; import { isBuild } from './utils/isBuild'; -import { getSpanFromRequest, withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; /** * Create a wrapped version of the user's exported `getServerSideProps` function @@ -32,27 +30,21 @@ export function wrapGetServerSidePropsWithSentry( dataFetchingMethodName: 'getServerSideProps', }); - const serverSideProps = await (tracedGetServerSideProps.apply(thisArg, args) as ReturnType< - typeof tracedGetServerSideProps - >); + const { + data: serverSideProps, + baggage, + sentryTrace, + } = await (tracedGetServerSideProps.apply(thisArg, args) as ReturnType); if (serverSideProps && 'props' in serverSideProps) { - const activeSpan = getActiveSpan(); - const requestSpan = getSpanFromRequest(req) ?? (activeSpan ? getRootSpan(activeSpan) : undefined); - if (requestSpan) { - const sentryTrace = spanToTraceHeader(requestSpan); - - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (sentryTrace) { - (serverSideProps.props as Record)._sentryTraceData = sentryTrace; - } - - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(requestSpan); - const baggage = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext); - // The Next.js serializer throws on undefined values so we need to guard for it (#12102) - if (baggage) { - (serverSideProps.props as Record)._sentryBaggage = baggage; - } + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (sentryTrace) { + (serverSideProps.props as Record)._sentryTraceData = sentryTrace; + } + + // The Next.js serializer throws on undefined values so we need to guard for it (#12102) + if (baggage) { + (serverSideProps.props as Record)._sentryBaggage = baggage; } } diff --git a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts index a50c25ac616b..aebbf42ac684 100644 --- a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts +++ b/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts @@ -17,7 +17,7 @@ export function wrapGetStaticPropsWithSentry( parameterizedRoute: string, ): GetStaticProps { return new Proxy(origGetStaticPropsa, { - apply: (wrappingTarget, thisArg, args: Parameters>) => { + apply: async (wrappingTarget, thisArg, args: Parameters>) => { if (isBuild()) { return wrappingTarget.apply(thisArg, args); } @@ -27,8 +27,6 @@ export function wrapGetStaticPropsWithSentry( parameterizedRoute, dataFetchingMethodName: 'getStaticProps', }); - - return errorWrappedGetStaticProps.apply(thisArg, args); }, }); }