diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b18da4f6c43f..d78ac48a795f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,6 +96,8 @@ jobs: profiling_node: - 'packages/profiling-node/**' - 'dev-packages/e2e-tests/test-applications/node-profiling/**' + any_code: + - '!**/*.md' - name: Get PR labels @@ -109,6 +111,8 @@ jobs: is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }} changed_profiling_node: ${{ steps.changed.outputs.profiling_node == 'true' }} changed_ci: ${{ steps.changed.outputs.workflow == 'true' }} + changed_any_code: ${{ steps.changed.outputs.any_code == 'true' }} + # When merging into master, or from master is_gitflow_sync: ${{ github.head_ref == 'master' || github.ref == 'refs/heads/master' }} has_gitflow_label: @@ -123,6 +127,7 @@ jobs: runs-on: ubuntu-20.04 timeout-minutes: 15 if: | + (needs.job_get_metadata.outputs.changed_any_code) && (needs.job_get_metadata.outputs.is_gitflow_sync == 'false' && needs.job_get_metadata.outputs.has_gitflow_label == 'false') steps: - name: Check out base commit (${{ github.event.pull_request.base.sha }}) @@ -274,7 +279,7 @@ jobs: job_check_format: name: Check file formatting - needs: [job_get_metadata, job_build] + needs: [job_get_metadata] timeout-minutes: 10 runs-on: ubuntu-20.04 steps: @@ -282,16 +287,29 @@ jobs: uses: actions/checkout@v4 with: ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node uses: actions/setup-node@v4 with: node-version-file: 'package.json' + + # we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed, + # so no need to reinstall them + - name: Compute dependency cache key + id: compute_lockfile_hash + run: echo "hash=${{ hashFiles('yarn.lock', '**/package.json') }}" >> "$GITHUB_OUTPUT" + - name: Check dependency cache - uses: actions/cache/restore@v4 + uses: actions/cache@v4 + id: cache_dependencies with: path: ${{ env.CACHED_DEPENDENCY_PATHS }} - key: ${{ needs.job_build.outputs.dependency_cache_key }} - fail-on-cache-miss: true + key: ${{ steps.compute_lockfile_hash.outputs.hash }} + + - name: Install dependencies + if: steps.cache_dependencies.outputs.cache-hit != 'true' + run: yarn install --ignore-engines --frozen-lockfile + - name: Check file formatting run: yarn lint:prettier && yarn lint:biome diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index 39fcedf174da..8fd9f4368977 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -17,7 +17,7 @@ "@types/node": "18.11.17", "@types/react": "18.0.26", "@types/react-dom": "18.0.9", - "next": "15.0.0-canary.77", + "next": "15.0.0-canary.112", "react": "beta", "react-dom": "beta", "typescript": "4.9.5" diff --git a/packages/nextjs/src/common/captureRequestError.ts b/packages/nextjs/src/common/captureRequestError.ts index 7968907ad9bf..8350a0f2e593 100644 --- a/packages/nextjs/src/common/captureRequestError.ts +++ b/packages/nextjs/src/common/captureRequestError.ts @@ -1,7 +1,7 @@ import { captureException, withScope } from '@sentry/core'; type RequestInfo = { - url: string; + path: string; method: string; headers: Record; }; @@ -33,7 +33,7 @@ export function experimental_captureRequestError( }); scope.setContext('nextjs', { - request_path: request.url, + request_path: request.path, router_kind: errorContext.routerKind, router_path: errorContext.routePath, route_type: errorContext.routeType, 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); }, }); } diff --git a/packages/vercel-edge/README.md b/packages/vercel-edge/README.md index 5f4744ac3c9d..56a6ca051887 100644 --- a/packages/vercel-edge/README.md +++ b/packages/vercel-edge/README.md @@ -4,7 +4,11 @@

-# Official Sentry SDK for Vercel Edge Runtime [ALPHA] +# Internal Sentry Utilities for Vercel Edge Runtime + +> **NOTICE:** It is discouraged to depend on this package directly. `@sentry/vercel-edge` is used as building block for +> higher level Sentry SDKs like `@sentry/nextjs`. The API of this `@sentry/vercel-edge` may break with any major and +> non-major version! [![npm version](https://img.shields.io/npm/v/@sentry/vercel-edge.svg)](https://www.npmjs.com/package/@sentry/vercel-edge) [![npm dm](https://img.shields.io/npm/dm/@sentry/vercel-edge.svg)](https://www.npmjs.com/package/@sentry/vercel-edge) @@ -12,50 +16,6 @@ ## Links -- [Official SDK Docs](https://docs.sentry.io/quickstart/) -- [TypeDoc](http://getsentry.github.io/sentry-javascript/) - -**Note: This SDK is still in an alpha state. Breaking changes can occur at any time.** - -## Usage - -To use this SDK, call `init(options)` as early as possible in the main entry module. This will initialize the SDK and -hook into the environment. Note that you can turn off almost all side effects using the respective options. - -```javascript -// CJS Syntax -const Sentry = require('@sentry/vercel-edge'); -// ESM Syntax -import * as Sentry from '@sentry/vercel-edge'; - -Sentry.init({ - dsn: '__DSN__', - // ... -}); -``` - -To set context information or send manual events, use the exported functions of `@sentry/vercel-edge`. Note that these -functions will not perform any action before you have called `init()`: - -```javascript -// Set user information, as well as tags and further extras -Sentry.setExtra('battery', 0.7); -Sentry.setTag('user_mode', 'admin'); -Sentry.setUser({ id: '4711' }); - -// Add a breadcrumb for future events -Sentry.addBreadcrumb({ - message: 'My Breadcrumb', - // ... -}); - -// Capture exceptions, messages or manual events -Sentry.captureMessage('Hello, world!'); -Sentry.captureException(new Error('Good bye')); -Sentry.captureEvent({ - message: 'Manual', - stacktrace: [ - // ... - ], -}); -``` +- [Sentry.io](https://sentry.io/?utm_source=github&utm_medium=npm_vercel_edge) +- [Sentry Discord Server](https://discord.gg/Ww9hbqr) +- [Stack Overflow](https://stackoverflow.com/questions/tagged/sentry)