diff --git a/.changeset/strong-crabs-shake.md b/.changeset/strong-crabs-shake.md new file mode 100644 index 00000000000..723e660b346 --- /dev/null +++ b/.changeset/strong-crabs-shake.md @@ -0,0 +1,5 @@ +--- +"web-app": minor +--- + +Updated to use @sentry/nextjs rather than @sentry/\*. Added monitoring routes. diff --git a/.github/workflows/ci-e2e-web-app.yml b/.github/workflows/ci-e2e-web-app.yml index 34ea36f5f1e..02c3e3ace5f 100644 --- a/.github/workflows/ci-e2e-web-app.yml +++ b/.github/workflows/ci-e2e-web-app.yml @@ -113,12 +113,11 @@ jobs: env: PRISMA_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/webapp-e2e?schema=public # Don't need to lint / typecheck for e2e, they're done in another workflow - NEXTJS_IGNORE_ESLINT: 1 - NEXTJS_IGNORE_TYPECHECK: 1 - NEXT_DISABLE_SOURCEMAPS: 1 - NEXT_TELEMETRY_DISABLED: 1 - NEXT_DISABLE_SENTRY: 0 - NEXT_SENTRY_DRY_RUN: 1 + NEXTJS_IGNORE_ESLINT: true + NEXTJS_IGNORE_TYPECHECK: true + NEXT_DISABLE_SOURCEMAPS: true + NEXT_TELEMETRY_DISABLED: true + NEXTJS_SENTRY_UPLOAD_DRY_RUN: true - name: Install Playwright run: npx playwright install --with-deps chromium webkit diff --git a/.github/workflows/ci-web-app.yml b/.github/workflows/ci-web-app.yml index 51e9075872c..0005648a12a 100644 --- a/.github/workflows/ci-web-app.yml +++ b/.github/workflows/ci-web-app.yml @@ -109,17 +109,15 @@ jobs: yarn build env: # Speed up build: they are linted in a previous step - NEXTJS_IGNORE_ESLINT: 1 + NEXTJS_IGNORE_ESLINT: true # Speed up build: they are typechecked in a previous step - NEXTJS_IGNORE_TYPECHECK: 1 + NEXTJS_IGNORE_TYPECHECK: true # Speed up build: don't run if not needed, enable if it becomes needed - NEXT_DISABLE_SOURCEMAPS: 1 + NEXT_DISABLE_SOURCEMAPS: true # Don't send telemetry for ci - NEXT_TELEMETRY_DISABLED: 1 + NEXT_TELEMETRY_DISABLED: true # Fully disable sentry registration here (no overhead in build time) - NEXT_DISABLE_SENTRY: 1 - # Disable sentry source map upload (when not needed) - NEXT_SENTRY_DRY_RUN: 1 + NEXTJS_SENTRY_UPLOAD_DRY_RUN: true - name: Check bundle size working-directory: apps/web-app diff --git a/.yarnrc.yml b/.yarnrc.yml index 9928280c98f..abe1a8dae16 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -20,6 +20,11 @@ packageExtensions: 'next-secure-headers@*': peerDependencies: react: ^17.0.0 + # link https://github.com/getsentry/sentry-javascript/pull/4634 + '@sentry/nextjs@*': + peerDependenciesMeta: + webpack: + optional: true # Allows limiting download of native binaries to specified platforms (ie: swc, esbuild...) # @link https://yarnpkg.com/configuration/yarnrc#supportedArchitectures diff --git a/apps/blog-app/package.json b/apps/blog-app/package.json index e1707a89223..065e8b3b221 100644 --- a/apps/blog-app/package.json +++ b/apps/blog-app/package.json @@ -30,11 +30,11 @@ "clean": "rimraf --no-glob ./.next ./out ./coverage ./tsconfig.tsbuildinfo ./.eslintcache && jest --clear-cache", "dev": "next", "build": "next build", - "build-no-checks": "NEXTJS_IGNORE_TYPECHECK=1 NEXTJS_IGNORE_ESLINT=1 next build", + "build-no-checks": "cross-env NEXTJS_IGNORE_TYPECHECK=1 NEXTJS_IGNORE_ESLINT=1 next build", "vercel-build": "yarn share-static-hardlink && next build", "export": "next export", "start": "next start", - "bundle-analyze": "cross-env ANALYZE=true yarn build", + "bundle-analyze": "cross-env ANALYZE=true NEXTJS_IGNORE_TYPECHECK=1 NEXTJS_IGNORE_ESLINT=1 yarn build", "?share-static-symlink": "echo 'Use this command to link assets, locales... from shared static folder'", "share-static-symlink": "rimraf ./public/shared-assets && symlink-dir ../../static/assets ./public/shared-assets", "?share-static-hardlink": "echo 'Use this command to link assets, locales... from shared static folder'", diff --git a/apps/web-app/.env b/apps/web-app/.env index 84896423d06..06e5603a6ad 100644 --- a/apps/web-app/.env +++ b/apps/web-app/.env @@ -7,5 +7,13 @@ # @see https://www.prisma.io/docs/concepts/components/prisma-client/deployment#recommended-connection-limit PRISMA_DATABASE_URL=postgresql://nextjs:!ChangeMe!@localhost:5432/maindb?schema=public + +# Sentry related +# Sourcemap upload to sentry is disabled by default +NEXTJS_SENTRY_UPLOAD_DRY_RUN=true +SENTRY_AUTH_TOKEN= +SENTRY_ORG=sebastien-vanvelthem +SENTRY_PROJECT=monorepo-web-app NEXT_PUBLIC_SENTRY_RELEASE= NEXT_PUBLIC_SENTRY_DSN= + diff --git a/apps/web-app/.gitignore b/apps/web-app/.gitignore index 93e98b50c7b..863484db680 100644 --- a/apps/web-app/.gitignore +++ b/apps/web-app/.gitignore @@ -29,3 +29,6 @@ yarn-error.log* .env.local .env.*.local + +# Sentry +.sentryclirc diff --git a/apps/web-app/.size-limit.js b/apps/web-app/.size-limit.js index 96a76d7b5e6..edbb85c6416 100644 --- a/apps/web-app/.size-limit.js +++ b/apps/web-app/.size-limit.js @@ -15,8 +15,8 @@ const limitCfg = { defaultSize: '120kb', pages: { // Customize specific page limits if needed - '/_app': '120kb', - '/_error': '105kb', + '/_app': '160kb', + '/_error': '80kb', '/404': '100kb', '/': '105kb', '/demo': '105kb', diff --git a/apps/web-app/e2e/pages/_monitor/sentry.spec.ts b/apps/web-app/e2e/pages/_monitor/sentry.spec.ts new file mode 100644 index 00000000000..5f5f5777ca4 --- /dev/null +++ b/apps/web-app/e2e/pages/_monitor/sentry.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Sentry monitor pages', () => { + test.describe('Client-side rendered', () => { + test('should have a title containing error', async ({ page }) => { + await page.goto('/_monitor/sentry/csr-page'); + await expect(page).toHaveTitle(/error/i); + }); + }); + + test.describe('Server-side rendered', () => { + test('should have a title containing error', async ({ page }) => { + await page.goto('/_monitor/sentry/ssr-page'); + await expect(page).toHaveTitle(/error/i); + }); + }); +}); diff --git a/apps/web-app/e2e/pages/api/_monitor/sentry.spec.ts b/apps/web-app/e2e/pages/api/_monitor/sentry.spec.ts new file mode 100644 index 00000000000..8413660973f --- /dev/null +++ b/apps/web-app/e2e/pages/api/_monitor/sentry.spec.ts @@ -0,0 +1,7 @@ +import { test, expect } from '@playwright/test'; + +test('should return a status of 500', async ({ request }) => { + const resp = await request.get('/api/_monitor/sentry'); + const status = resp.status(); + expect(status).toEqual(500); +}); diff --git a/apps/web-app/next.config.js b/apps/web-app/next.config.js index 7edf62bd92f..57ed58164f4 100644 --- a/apps/web-app/next.config.js +++ b/apps/web-app/next.config.js @@ -1,10 +1,11 @@ // @ts-check +// https://nextjs.org/docs/api-reference/next.config.js/introduction +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ +const { withSentryConfig } = require('@sentry/nextjs'); const pc = require('picocolors'); - -const { i18n } = require('./next-i18next.config'); - const packageJson = require('./package.json'); +const { i18n } = require('./next-i18next.config'); const trueEnv = ['true', '1', 'yes']; @@ -18,6 +19,10 @@ const NEXTJS_IGNORE_TYPECHECK = trueEnv.includes( process.env?.NEXTJS_IGNORE_TYPECHECK ?? 'false' ); +const NEXTJS_SENTRY_UPLOAD_DRY_RUN = trueEnv.includes( + process.env?.NEXTJS_SENTRY_UPLOAD_DRY_RUN ?? 'false' +); + /** * A way to allow CI optimization when the build done there is not used * to deliver an image or deploy the files. @@ -96,7 +101,7 @@ const nextConfig = { }, // @link https://nextjs.org/docs/advanced-features/compiler#minification - swcMinify: true, + swcMinify: false, experimental: { // React 18 @@ -161,11 +166,6 @@ const nextConfig = { */ webpack: (config, { isServer }) => { - if (!isServer) { - // Swap sentry/node by sentry/browser - config.resolve.alias['@sentry/node'] = '@sentry/browser'; - } - if (isServer) { // Till undici 4 haven't landed in prisma, we need this for docker/alpine // @see https://github.com/prisma/prisma/issues/6925#issuecomment-905935585 @@ -223,6 +223,18 @@ if (tmModules.length > 0) { config = nextConfig; } +config = withSentryConfig(config, { + // Additional config options for the Sentry Webpack plugin. Keep in mind that + // the following options are set automatically, and overriding them is not + // recommended: + // release, url, org, project, authToken, configFile, stripPrefix, + // urlPrefix, include, ignore + // For all available options, see: + // https://github.com/getsentry/sentry-webpack-plugin#options. + // silent: isProd, // Suppresses all logs + dryRun: NEXTJS_SENTRY_UPLOAD_DRY_RUN, +}); + if (process.env.ANALYZE === 'true') { // @ts-ignore const withBundleAnalyzer = require('@next/bundle-analyzer')({ diff --git a/apps/web-app/package.json b/apps/web-app/package.json index 748140bc633..4bc27eaa90c 100644 --- a/apps/web-app/package.json +++ b/apps/web-app/package.json @@ -30,10 +30,10 @@ "clean": "rimraf --no-glob ./.next ./out ./coverage ./tsconfig.tsbuildinfo ./.eslintcache && jest --clear-cache", "dev": "next", "build": "next build", - "build-no-checks": "NEXTJS_IGNORE_TYPECHECK=1 NEXTJS_IGNORE_ESLINT=1 next build", + "build-no-checks": "cross-env NEXTJS_IGNORE_TYPECHECK=1 NEXTJS_IGNORE_ESLINT=1 NEXTJS_SENTRY_UPLOAD_DRY_RUN=1 next build", "vercel-build": "yarn share-static-hardlink && next build", "start": "next start", - "bundle-analyze": "cross-env ANALYZE=true yarn build", + "bundle-analyze": "cross-env ANALYZE=true NEXTJS_IGNORE_TYPECHECK=1 NEXTJS_IGNORE_ESLINT=1 NEXTJS_SENTRY_UPLOAD_DRY_RUN=1 yarn build", "?share-static-symlink": "echo 'Use this command to link assets, locales... from shared static folder'", "share-static-symlink": "rimraf ./public/shared-assets && symlink-dir ../../static/assets ./public/shared-assets", "?share-static-hardlink": "echo 'Use this command to link assets, locales... from shared static folder'", @@ -117,8 +117,8 @@ "@headlessui/react": "1.5.0", "@mui/icons-material": "5.5.1", "@mui/material": "5.5.1", - "@sentry/browser": "6.18.2", - "@sentry/node": "6.18.2", + "@sentry/nextjs": "6.18.2", + "@sentry/react": "6.18.2", "@soluble/cache-ioredis": "0.8.6", "@tsed/exceptions": "6.106.0", "@your-org/core-lib": "workspace:^", diff --git a/apps/web-app/sentry.client.config.ts b/apps/web-app/sentry.client.config.ts new file mode 100644 index 00000000000..3ab29366fc9 --- /dev/null +++ b/apps/web-app/sentry.client.config.ts @@ -0,0 +1,39 @@ +// This file configures the initialization of Sentry on the browser. +// The config you add here will be used whenever a page is visited. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +// Bundle size optimization +// - To avoid bundling @sentry/tracing (17Kb), it's possible to +// import '@sentry/react' rather than '@sentry/nextjs'. +// +// import { init as sentryInit } from '@sentry/nextjs'; +import { init as sentryInit } from '@sentry/react'; + +sentryInit({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + + // Adjust this value in production, or use tracesSampler for greater control + // @see https://develop.sentry.dev/sdk/performance/ + // To turn it off, remove the line + // @see https://github.com/getsentry/sentry-javascript/discussions/4503#discussioncomment-2143116 + // tracesSampleRate: 1.0, + + // ... + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps + beforeSend: async (event, hint) => { + if (process.env.NODE_ENV === 'development') { + console.log('Sentry event', event); + console.log('Sentry hint', hint); + } + return event; + }, + ignoreErrors: [ + /** + * @link https://github.com/WICG/ResizeObserver/issues/38#issuecomment-422126006, + * @link https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded/50387233#50387233 + */ + 'ResizeObserver loop limit exceeded', + ], +}); diff --git a/apps/web-app/sentry.server.config.ts b/apps/web-app/sentry.server.config.ts new file mode 100644 index 00000000000..44d95d1ad3f --- /dev/null +++ b/apps/web-app/sentry.server.config.ts @@ -0,0 +1,26 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import { init as sentryInit } from '@sentry/nextjs'; + +sentryInit({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + + // Adjust this value in production, or use tracesSampler for greater control + // @see https://develop.sentry.dev/sdk/performance/ + // To turn it off, remove the line + // @see https://github.com/getsentry/sentry-javascript/discussions/4503#discussioncomment-2143116 + tracesSampleRate: 1.0, + // ... + // Note: if you want to override the automatic release value, do not set a + // `release` value here - use the environment variable `SENTRY_RELEASE`, so + // that it will also get attached to your source maps + beforeSend: async (event, hint) => { + if (process.env.NODE_ENV === 'development') { + console.log('Sentry event', event); + console.log('Sentry hint', hint); + } + return event; + }, +}); diff --git a/apps/web-app/src/config/sentry.config.ts b/apps/web-app/src/config/sentry.config.ts deleted file mode 100644 index e041b79df91..00000000000 --- a/apps/web-app/src/config/sentry.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { BrowserOptions } from '@sentry/browser'; -export const sentryBrowserInitConfig: BrowserOptions = { - dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - release: [process.env.APP_NAME, process.env.APP_VERSION].join(':'), - tracesSampleRate: 1.0, - debug: false, - ignoreErrors: [ - /** - * @link https://github.com/WICG/ResizeObserver/issues/38#issuecomment-422126006, - * @link https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded/50387233#50387233 - */ - 'ResizeObserver loop limit exceeded', - ], -}; diff --git a/apps/web-app/src/features/error/error.config.ts b/apps/web-app/src/features/error/error.config.ts new file mode 100644 index 00000000000..a34e2f201ab --- /dev/null +++ b/apps/web-app/src/features/error/error.config.ts @@ -0,0 +1,11 @@ +import type { I18nActiveNamespaces } from '@/core/i18n/i18n-namespaces.type'; + +export type ErrorConfig = { + // Define installed namespaces in the type here + // to allow full typechecking of your translation keys. + i18nNamespaces: Readonly>; +}; + +export const errorConfig: ErrorConfig = { + i18nNamespaces: ['system'], +} as const; diff --git a/apps/web-app/src/features/error/pages/error.page.tsx b/apps/web-app/src/features/error/pages/error.page.tsx new file mode 100644 index 00000000000..4ff28412d84 --- /dev/null +++ b/apps/web-app/src/features/error/pages/error.page.tsx @@ -0,0 +1,28 @@ +import Head from 'next/head'; +import type { FC } from 'react'; + +type Props = { + statusCode?: number | null; + error?: Error; + message?: string; + sentryErrorId?: string; + children?: never; +}; + +export const ErrorPage: FC = (props) => { + const { error, sentryErrorId, message, statusCode } = props; + + return ( + <> + + Error {statusCode} + +
+

Error {statusCode}

+

{message}

+

Sentry id: {sentryErrorId}

+

Error: {error?.message}

+
+ + ); +}; diff --git a/apps/web-app/src/pages/_app.tsx b/apps/web-app/src/pages/_app.tsx index de4907fb994..154ceeb59cf 100644 --- a/apps/web-app/src/pages/_app.tsx +++ b/apps/web-app/src/pages/_app.tsx @@ -1,10 +1,7 @@ import type { EmotionCache } from '@emotion/react'; -import * as Sentry from '@sentry/browser'; -import { isNonEmptyString } from '@your-org/core-lib'; import { appWithTranslation } from 'next-i18next'; import type { AppProps as NextAppProps } from 'next/app'; import Head from 'next/head'; -import { sentryBrowserInitConfig } from '@/config/sentry.config'; import { AppProviders } from '../app-providers'; /** @@ -29,13 +26,6 @@ export type AppProps = NextAppProps & { emotionCache?: EmotionCache; }; -if ( - process.env.NEXT_PUBLIC_SENTRY_DSN && - isNonEmptyString(process.env.NEXT_PUBLIC_SENTRY_DSN) -) { - Sentry.init(sentryBrowserInitConfig); -} - /** * @link https://nextjs.org/docs/advanced-features/custom-app */ diff --git a/apps/web-app/src/pages/_error.tsx b/apps/web-app/src/pages/_error.tsx index 5159428c16d..b37ea76b52a 100644 --- a/apps/web-app/src/pages/_error.tsx +++ b/apps/web-app/src/pages/_error.tsx @@ -3,17 +3,23 @@ * @link https://nextjs.org/docs/advanced-features/custom-error-page */ -import * as Sentry from '@sentry/node'; -import { isNonEmptyString } from '@your-org/core-lib'; +import { + captureException as sentryCaptureException, + flush as sentryFlush, +} from '@sentry/nextjs'; import type { NextPage, NextPageContext } from 'next'; import NextErrorComponent from 'next/error'; import type { ErrorProps } from 'next/error'; +import { ErrorPage } from '@/features/error/pages/error.page'; + +const sentryIgnoredStatusCodes: number[] = [404, 410]; // Adds HttpException to the list of possible error types. type AugmentedError = NonNullable | null; type CustomErrorProps = { err?: AugmentedError; message?: string; + sentryErrorId?: string; hasGetInitialPropsRun?: boolean; } & Omit; @@ -21,44 +27,63 @@ type AugmentedNextPageContext = Omit & { err: AugmentedError; }; -const captureException = async (err: string | Error) => { - if ( - process.env.NEXT_PUBLIC_SENTRY_DSN && - isNonEmptyString(process.env.NEXT_PUBLIC_SENTRY_DSN) - ) { - Sentry.captureException(err); +/** + * The request to sentry might be blocked on the browser due to ad blockers, csrf... + * Alternatively a good practice is to proxy the sentry in a nextjs api route, istio... + * @see https://github.com/getsentry/sentry-javascript/issues/2916 + */ +const sentryCaptureExceptionFailsafe = ( + err: Error | string +): string | undefined => { + let browserSentryErrorId: string | undefined; + try { + browserSentryErrorId = sentryCaptureException(err); + } catch (e) { + const msg = `Couldn't send error to sentry, reason ${ + e instanceof Error ? e.message : 'unknown' + }`; + console.error(msg); } + return browserSentryErrorId; }; -const captureExceptionAndFlush = async ( - err: string | Error, - flushAfter = 2000 -) => { - if ( - process.env.NEXT_PUBLIC_SENTRY_DSN && - isNonEmptyString(process.env.NEXT_PUBLIC_SENTRY_DSN) - ) { - Sentry.captureException(err); - if (flushAfter > 0) { - // Flushing before returning is necessary if deploying to Vercel, see - // https://vercel.com/docs/platform/limits#streaming-responses - await Sentry.flush(flushAfter); +/** + * Flushing the request on the browser is not required and might fail with err:BLOCKED_BY_CLIENT + * Possible causes vary, but the most common is that the request is blocked by ad-blockers or csrf rules. + */ +const sentryFlushServerSide = async (flushAfter: number) => { + if (typeof window === 'undefined') { + try { + await sentryFlush(flushAfter); + } catch (e) { + const msg = `Couldn't flush sentry, reason ${ + e instanceof Error ? e.message : 'unknown' + }`; + console.error(msg); } } }; const CustomError: NextPage = (props) => { - const { statusCode, err, hasGetInitialPropsRun } = props; + const { statusCode, err, hasGetInitialPropsRun, sentryErrorId, message } = + props; + + let browserSentryErrorId: string | undefined; if (!hasGetInitialPropsRun && err) { // getInitialProps is not called in case of https://github.com/vercel/next.js/issues/8592. // As a workaround, we pass err via _app.js so it can be captured - + browserSentryErrorId = sentryCaptureExceptionFailsafe(err); // Flushing is not required in this case as it only happens on the client - captureException(err); } - - return ; + return ( + + ); }; CustomError.getInitialProps = async ({ @@ -75,12 +100,20 @@ CustomError.getInitialProps = async ({ // getInitialProps has run errorInitialProps.hasGetInitialPropsRun = true; + // Returning early because we don't want to log ignored errors to Sentry. + if ( + typeof res?.statusCode === 'number' && + sentryIgnoredStatusCodes.includes(res.statusCode) + ) { + return errorInitialProps; + } + // Running on the server, the response object (`res`) is available. // - // Next.js will pass an err on the server if a page's data fetching methods + // Next.js will pass an error on the server if a page's data fetching methods // threw or returned a Promise that rejected // - // Running on the client (browser), Next.js will provide an err if: + // Running on the client (browser), Next.js will provide an error if: // // - a page's `getInitialProps` threw or returned a Promise that rejected // - an exception was thrown somewhere in the React lifecycle (render, @@ -89,23 +122,20 @@ CustomError.getInitialProps = async ({ // Boundaries: https://reactjs.org/docs/error-boundaries.html if (err) { + errorInitialProps.sentryErrorId = sentryCaptureExceptionFailsafe(err); // Flushing before returning is necessary if deploying to Vercel, see // https://vercel.com/docs/platform/limits#streaming-responses - await captureExceptionAndFlush(err, 2000); + await sentryFlushServerSide(1_500); return errorInitialProps; } // If this point is reached, getInitialProps was called without any // information about what the error might be. This is unexpected and may // indicate a bug introduced in Next.js, so record it in Sentry - // - // Flushing before returning is necessary if deploying to Vercel, see - // https://vercel.com/docs/platform/limits#streaming-responses - await captureExceptionAndFlush( - new Error(`_error.js getInitialProps missing data at path: ${asPath}`), - 2000 + errorInitialProps.sentryErrorId = sentryCaptureException( + new Error(`_error.js getInitialProps missing data at path: ${asPath}`) ); - + await sentryFlushServerSide(1_500); return errorInitialProps; }; diff --git a/apps/web-app/src/pages/_monitor/sentry/csr-page.tsx b/apps/web-app/src/pages/_monitor/sentry/csr-page.tsx new file mode 100644 index 00000000000..12554e3d928 --- /dev/null +++ b/apps/web-app/src/pages/_monitor/sentry/csr-page.tsx @@ -0,0 +1,29 @@ +import { usePromise } from '@your-org/core-lib/hooks'; +import type { FC } from 'react'; + +const fetchAndAlwaysThrow = async () => { + throw new Error( + 'Error purposely crafted for monitoring sentry (/pages/_monitor/sentry/csr-page.tsx)' + ); +}; + +const MonitorSentryCsrRoute: FC<{ children?: never }> = () => { + const { error } = usePromise(fetchAndAlwaysThrow, {}); + if (error) { + throw error; + } + return ( +
+

Unexpected error

+

+ If you see this message, it means that an error thrown in a static + NextJs page wasn't caught by the global error handler + (pages/_error.tsx). This is a bug in the application and may affect the + ability to display error pages and log errors on Sentry. See the + monitoring page in /pages/_monitor/sentry/csr-page.tsx. +

+
+ ); +}; + +export default MonitorSentryCsrRoute; diff --git a/apps/web-app/src/pages/_monitor/sentry/ssr-page.tsx b/apps/web-app/src/pages/_monitor/sentry/ssr-page.tsx new file mode 100644 index 00000000000..083632df5c6 --- /dev/null +++ b/apps/web-app/src/pages/_monitor/sentry/ssr-page.tsx @@ -0,0 +1,33 @@ +import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'; + +type Props = { + hasRunOnServer: boolean; +}; + +export default function MonitorSentrySsrRoute( + _props: InferGetServerSidePropsType +) { + return ( +
+

Unexpected error

+

+ If you see this message, it means that the an error thrown in the + `getServerSideProps()` function wasn't caught by the global error + handler (pages/_error.tsx). This is a bug in the application and may + affect the ability to display error pages and log errors on Sentry. See + the monitoring page in /pages/_monitor/sentry/ssr-page.tsx. +

+
+ ); +} + +/** + * Always throws an error on purpose for monitoring + */ +export const getServerSideProps: GetServerSideProps = async ( + _context +) => { + throw new Error( + 'Error purposely crafted for monitoring sentry (/pages/_monitor/sentry/ssr-page.tsx)' + ); +}; diff --git a/apps/web-app/src/pages/api/_monitor/healthcheck.ts b/apps/web-app/src/pages/api/_monitor/healthcheck.ts index ad3af14e839..5c5602e7d28 100644 --- a/apps/web-app/src/pages/api/_monitor/healthcheck.ts +++ b/apps/web-app/src/pages/api/_monitor/healthcheck.ts @@ -8,7 +8,7 @@ export type HealthCheckApiPayload = { timestamp: string; }; -export default async function healthCheckRoute( +export default async function healthCheckApiRoute( req: NextApiRequest, res: NextApiResponse ) { diff --git a/apps/web-app/src/pages/api/_monitor/sentry.ts b/apps/web-app/src/pages/api/_monitor/sentry.ts new file mode 100644 index 00000000000..0b55db40d55 --- /dev/null +++ b/apps/web-app/src/pages/api/_monitor/sentry.ts @@ -0,0 +1,12 @@ +import { withSentry } from '@sentry/nextjs'; +import type { NextApiRequest, NextApiResponse } from 'next'; + +async function sentryMonitorApiRoute( + _req: NextApiRequest, + _res: NextApiResponse +) { + throw new Error( + 'Error purposely crafted for monitoring sentry (/pages/api/_monitor/sentry.tsx)' + ); +} +export default withSentry(sentryMonitorApiRoute); diff --git a/yarn.lock b/yarn.lock index 5660f9e80ef..55867a3c33c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3931,6 +3931,23 @@ __metadata: languageName: node linkType: hard +"@sentry/cli@npm:^1.73.0": + version: 1.73.2 + resolution: "@sentry/cli@npm:1.73.2" + dependencies: + https-proxy-agent: ^5.0.0 + mkdirp: ^0.5.5 + node-fetch: ^2.6.7 + npmlog: ^4.1.2 + progress: ^2.0.3 + proxy-from-env: ^1.1.0 + which: ^2.0.2 + bin: + sentry-cli: bin/sentry-cli + checksum: b4dcd1391dcfb3aa400bd16788a5fc9266b7c1f237eabdc419d032f290d02983b1fe3932b40f7f30b23baffe7cbf310f897f42b51948fffd087f6e4df48a63d6 + languageName: node + linkType: hard + "@sentry/core@npm:6.18.2": version: 6.18.2 resolution: "@sentry/core@npm:6.18.2" @@ -3955,6 +3972,18 @@ __metadata: languageName: node linkType: hard +"@sentry/integrations@npm:6.18.2": + version: 6.18.2 + resolution: "@sentry/integrations@npm:6.18.2" + dependencies: + "@sentry/types": 6.18.2 + "@sentry/utils": 6.18.2 + localforage: ^1.8.1 + tslib: ^1.9.3 + checksum: 2345ea88e1d3bb34328b383ddd0ff3574c0b918e2f00f4cefd0042bb27c49e359b80b4013acb8b571948994259c70dfcbc99d0ab51a796ddd2038eac76503ad6 + languageName: node + linkType: hard + "@sentry/minimal@npm:6.18.2": version: 6.18.2 resolution: "@sentry/minimal@npm:6.18.2" @@ -3966,6 +3995,30 @@ __metadata: languageName: node linkType: hard +"@sentry/nextjs@npm:6.18.2": + version: 6.18.2 + resolution: "@sentry/nextjs@npm:6.18.2" + dependencies: + "@sentry/core": 6.18.2 + "@sentry/hub": 6.18.2 + "@sentry/integrations": 6.18.2 + "@sentry/node": 6.18.2 + "@sentry/react": 6.18.2 + "@sentry/tracing": 6.18.2 + "@sentry/utils": 6.18.2 + "@sentry/webpack-plugin": 1.18.8 + tslib: ^1.9.3 + peerDependencies: + next: ^10.0.8 || ^11.0 || ^12.0 + react: 15.x || 16.x || 17.x + webpack: ">= 4.0.0" + peerDependenciesMeta: + webpack: + optional: true + checksum: 3146e8628bb21934510fae8adb7e9ba92e7906f8fb8ada3bde99ac3b8ef236556cec0b4bd61bafe7d77ee0f1c298b06e89d839c496c162a3f8c5c0b44428e895 + languageName: node + linkType: hard + "@sentry/node@npm:6.18.2": version: 6.18.2 resolution: "@sentry/node@npm:6.18.2" @@ -3982,6 +4035,35 @@ __metadata: languageName: node linkType: hard +"@sentry/react@npm:6.18.2": + version: 6.18.2 + resolution: "@sentry/react@npm:6.18.2" + dependencies: + "@sentry/browser": 6.18.2 + "@sentry/minimal": 6.18.2 + "@sentry/types": 6.18.2 + "@sentry/utils": 6.18.2 + hoist-non-react-statics: ^3.3.2 + tslib: ^1.9.3 + peerDependencies: + react: 15.x || 16.x || 17.x + checksum: f4c775fa4a8b23363ac5a13386a6297873d721bcd28d0dc563631e1a099ddac75dfacb0bf2fde5c54cbbc1952b80734d57da1d4cac129df87fc55e9d4364af82 + languageName: node + linkType: hard + +"@sentry/tracing@npm:6.18.2": + version: 6.18.2 + resolution: "@sentry/tracing@npm:6.18.2" + dependencies: + "@sentry/hub": 6.18.2 + "@sentry/minimal": 6.18.2 + "@sentry/types": 6.18.2 + "@sentry/utils": 6.18.2 + tslib: ^1.9.3 + checksum: fe923e68d466de36344298634d81a1bb4acc0ab3e8b8baee12f41beb2a07ff477b428393d12c1db386c65fff182dd05d7a57d9ab6a15fc8c0b95f9195f4878cc + languageName: node + linkType: hard + "@sentry/types@npm:6.18.2": version: 6.18.2 resolution: "@sentry/types@npm:6.18.2" @@ -3999,6 +4081,15 @@ __metadata: languageName: node linkType: hard +"@sentry/webpack-plugin@npm:1.18.8": + version: 1.18.8 + resolution: "@sentry/webpack-plugin@npm:1.18.8" + dependencies: + "@sentry/cli": ^1.73.0 + checksum: e159ee1e4b486a6d602bcf04e08ce4f167c9148aa4b612f6871f81954fba0cf607297a77bcda22f6f882266ac715ce8cb7be55858eaebc9fdbb85737ae92eb04 + languageName: node + linkType: hard + "@sindresorhus/is@npm:^4.0.0": version: 4.2.0 resolution: "@sindresorhus/is@npm:4.2.0" @@ -14396,6 +14487,13 @@ __metadata: languageName: node linkType: hard +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: f9b3486477555997657f70318cc8d3416159f208bec4cca3ff3442fd266bc23f50f0c9bd8547e1371a6b5e82b821ec9a7044a4f7b944798b25aa3cc6d5e63e62 + languageName: node + linkType: hard + "immutable@npm:^4.0.0": version: 4.0.0 resolution: "immutable@npm:4.0.0" @@ -16423,6 +16521,15 @@ __metadata: languageName: node linkType: hard +"lie@npm:3.1.1": + version: 3.1.1 + resolution: "lie@npm:3.1.1" + dependencies: + immediate: ~3.0.5 + checksum: 6da9f2121d2dbd15f1eca44c0c7e211e66a99c7b326ec8312645f3648935bc3a658cf0e9fa7b5f10144d9e2641500b4f55bd32754607c3de945b5f443e50ddd1 + languageName: node + linkType: hard + "lilconfig@npm:2.0.4, lilconfig@npm:^2.0.3, lilconfig@npm:^2.0.4": version: 2.0.4 resolution: "lilconfig@npm:2.0.4" @@ -16541,6 +16648,15 @@ __metadata: languageName: node linkType: hard +"localforage@npm:^1.8.1": + version: 1.10.0 + resolution: "localforage@npm:1.10.0" + dependencies: + lie: 3.1.1 + checksum: f2978b434dafff9bcb0d9498de57d97eba165402419939c944412e179cab1854782830b5ec196212560b22712d1dd03918939f59cf1d4fc1d756fca7950086cf + languageName: node + linkType: hard + "locate-path@npm:^2.0.0": version: 2.0.0 resolution: "locate-path@npm:2.0.0" @@ -18056,7 +18172,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3": +"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.3, mkdirp@npm:^0.5.5": version: 0.5.5 resolution: "mkdirp@npm:0.5.5" dependencies: @@ -20713,7 +20829,7 @@ __metadata: languageName: node linkType: hard -"progress@npm:2.0.3": +"progress@npm:2.0.3, progress@npm:^2.0.3": version: 2.0.3 resolution: "progress@npm:2.0.3" checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7 @@ -20827,7 +20943,7 @@ __metadata: languageName: node linkType: hard -"proxy-from-env@npm:1.1.0": +"proxy-from-env@npm:1.1.0, proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4 @@ -25688,8 +25804,8 @@ __metadata: "@next/bundle-analyzer": 12.1.1-canary.10 "@next/env": 12.1.1-canary.10 "@playwright/test": 1.20.0 - "@sentry/browser": 6.18.2 - "@sentry/node": 6.18.2 + "@sentry/nextjs": 6.18.2 + "@sentry/react": 6.18.2 "@size-limit/file": 7.0.8 "@soluble/cache-ioredis": 0.8.6 "@svgr/webpack": 6.2.1