Skip to content

Commit

Permalink
feat: enable sentry features (nodejs#6133)
Browse files Browse the repository at this point in the history
* feat: enable sentry features

* feat: fine-tuned boundaries and filtering

* chore: use <anonymous>

* chore: add sample rates

* feat: error page and boundaries

* chore: sample rate and allow urls

* chore: attempt to use tunnel

* feat: load ondemand

* chore: do not provide error details to users

* chore: fixed a comment
  • Loading branch information
ovflowd authored Nov 23, 2023
1 parent 5882ccb commit 7fc2bca
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 28 deletions.
12 changes: 12 additions & 0 deletions app/[locale]/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use client';

import type { FC } from 'react';

const ErrorPage: FC<{ error: Error }> = () => (
<div className="container">
<h2>500: Internal Server Error</h2>
<h3>This Page has thrown a non-recoverable Error</h3>
</div>
);

export default ErrorPage;
2 changes: 1 addition & 1 deletion app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const RootLayout: FC<PropsWithChildren> = ({ children }) => {

return (
<html className={sourceSans.className} dir={langDir} lang={hrefLang}>
<body>
<body suppressHydrationWarning>
<LocaleProvider>
<ThemeProvider>
<BaseLayout>{children}</BaseLayout>
Expand Down
6 changes: 5 additions & 1 deletion components/withNodeRelease.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ export const WithNodeRelease: FC<WithNodeReleaseProps> = ({
[status].flat().includes(release.status)
);

return <Component release={matchingRelease!} />;
if (matchingRelease !== undefined) {
return <Component release={matchingRelease!} />;
}

return null;
};
2 changes: 2 additions & 0 deletions layouts/BaseLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client';

import type { FC, PropsWithChildren } from 'react';

import Footer from '@/components/Footer';
Expand Down
2 changes: 0 additions & 2 deletions next-data/generateNodeReleasesJson.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ const generateNodeReleasesJson = async () => {
};
});

console.timeEnd('g');

return writeFile(
jsonFilePath,
JSON.stringify(
Expand Down
16 changes: 12 additions & 4 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import { withSentryConfig } from '@sentry/nextjs';
import withNextIntl from 'next-intl/plugin';
import webpack from 'webpack';

import {
BASE_PATH,
ENABLE_STATIC_EXPORT,
SENTRY_DSN,
SENTRY_ENABLE,
SENTRY_EXTENSIONS,
SENTRY_TUNNEL,
} from './next.constants.mjs';
import { redirects, rewrites } from './next.rewrites.mjs';

Expand Down Expand Up @@ -44,15 +47,20 @@ const nextConfig = {
// as we already check it on the CI within each Pull Request
// we also configure ESLint to run its lint checking on all files (next lint)
eslint: { dirs: ['.'], ignoreDuringBuilds: true },
// Next.js WebPack Bundler does not know how to handle `.mjs` files on `node_modules`
// This is not an issue when using TurboPack as it uses SWC and it is ESM-only
// Once Next.js uses Turbopack for their build process we can remove this
// Adds custom WebPack configuration to our Next.hs setup
webpack: function (config) {
// Next.js WebPack Bundler does not know how to handle `.mjs` files on `node_modules`
// This is not an issue when using TurboPack as it uses SWC and it is ESM-only
// Once Next.js uses Turbopack for their build process we can remove this
config.module.rules.push({
test: /\.m?js$/,
type: 'javascript/auto',
resolve: { fullySpecified: false },
});

// Tree-shakes modules from Sentry Bundle
config.plugins.push(new webpack.DefinePlugin(SENTRY_EXTENSIONS));

return config;
},
experimental: {
Expand Down Expand Up @@ -92,7 +100,7 @@ const sentryConfig = {
// Upload Next.js or third-party code in addition to our code
widenClientFileUpload: true,
// Attempt to circumvent ad blockers
tunnelRoute: !ENABLE_STATIC_EXPORT && '/monitoring',
tunnelRoute: SENTRY_TUNNEL(),
// Prevent source map comments in built files
hideSourceMaps: false,
// Tree shake Sentry stuff from the bundle
Expand Down
27 changes: 27 additions & 0 deletions next.constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,30 @@ export const SENTRY_DSN =
*/
export const SENTRY_ENABLE =
process.env.NODE_ENV === 'development' || !!VERCEL_ENV;

/**
* This configures the sampling rate for Sentry
*
* @note we always want to capture 100% on preview branches and development mode
*/
export const SENTRY_CAPTURE_RATE =
SENTRY_ENABLE && BASE_URL !== 'https://nodejs.org' ? 1.0 : 0.01;

/**
* Provides the Route for Sentry's Server-Side Tunnel
*/
export const SENTRY_TUNNEL = (components = '') =>
SENTRY_ENABLE ? `/monitoring${components}` : undefined;

/**
* This configures which Sentry features to tree-shake/remove from the Sentry bundle
*
* @see https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/tree-shaking/
*/
export const SENTRY_EXTENSIONS = {
__SENTRY_DEBUG__: false,
__SENTRY_TRACING__: false,
__RRWEB_EXCLUDE_IFRAME__: true,
__RRWEB_EXCLUDE_SHADOW_DOM__: true,
__SENTRY_EXCLUDE_REPLAY_WORKER__: true,
};
83 changes: 67 additions & 16 deletions sentry.client.config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,68 @@
import { SENTRY_DSN, SENTRY_ENABLE } from '@/next.constants.mjs';
import {
Dedupe,
Breadcrumbs,
HttpContext,
LinkedErrors,
BrowserClient,
getCurrentHub,
defaultStackParser,
makeFetchTransport,
} from '@sentry/nextjs';

// This lazy-loads Sentry on the Browser
import('@sentry/nextjs').then(({ init }) =>
init({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Tell Sentry where to send events
dsn: SENTRY_DSN,
// Disable Sentry Tracing as we don't need to have it
// as Vercel already does Web Vitals and Performance Measurement on Client-Side
enableTracing: false,
// We only want to capture errors from _next folder on production
// We don't want to capture errors from preview branches here
allowUrls: ['https://nodejs.org/_next'],
})
);
import {
SENTRY_DSN,
SENTRY_ENABLE,
SENTRY_CAPTURE_RATE,
SENTRY_TUNNEL,
} from '@/next.constants.mjs';

// This creates a custom Sentry Client with minimal integrations
export const sentryClient = new BrowserClient({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Provide Sentry's Secret Key
dsn: SENTRY_DSN,
// Sentry's Error Transport Mechanism
transport: makeFetchTransport,
// Sentry's Stack Trace Parser
stackParser: defaultStackParser,
// All supported Integrations by us
integrations: [
new Dedupe(),
new HttpContext(),
new Breadcrumbs(),
new LinkedErrors(),
],
// We only want to allow ingestion from these pre-selected allowed URLs
// Note that the vercel.app prefix is for our Pull Request Branch Previews
allowUrls: ['https://nodejs.org/', /^https:\/\/.+\.vercel\.app/],
// Percentage of events to send to Sentry (1% of them) (for performance metrics)
tracesSampleRate: SENTRY_CAPTURE_RATE,
// Percentage of events to send to Sentry (1% of them) (for session replays)
replaysSessionSampleRate: SENTRY_CAPTURE_RATE,
// Percentage of events to send to Sentry (1% of them) (for session replays when error happens)
replaysOnErrorSampleRate: 1.0,
// Provides a custom Sentry Tunnel Router
// @note these are components of the Sentry DSN string
// @see @sentry/nextjs/esm/client/tunnelRoute.js
tunnel: SENTRY_TUNNEL(`?o=4506191161786368&p=4506191307735040`),
// Adds custom filtering before sending an Event to Sentry
beforeSend: (event, hint) => {
// Attempts to grab the original Exception before any "magic" happens
const exception = hint.originalException as Error;

// We only want to capture Errors that have a Stack Trace and that are not Anonymous Errors
return exception?.stack && !exception.stack.includes('<anonymous>')
? event
: null;
},
});

// Attaches this Browser Client to Sentry
getCurrentHub().bindClient(sentryClient);

// Loads this Dynamically to avoid adding this to the main bundle (initial load)
import('@sentry/nextjs').then(({ Replay, BrowserTracing }) => {
sentryClient.addIntegration(new Replay({ maskAllText: false }));
sentryClient.addIntegration(new BrowserTracing());
});
2 changes: 1 addition & 1 deletion sentry.edge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SENTRY_DSN, SENTRY_ENABLE } from '@/next.constants.mjs';
init({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Tell Sentry where to send events
// Provide Sentry's Secret Key
dsn: SENTRY_DSN,
// Percentage of events to send to Sentry (1% of them) (for performance metrics)
tracesSampleRate: 0.01,
Expand Down
10 changes: 7 additions & 3 deletions sentry.server.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { init } from '@sentry/nextjs';
import { ProfilingIntegration } from '@sentry/profiling-node';

import { SENTRY_DSN, SENTRY_ENABLE } from '@/next.constants.mjs';
import {
SENTRY_DSN,
SENTRY_ENABLE,
SENTRY_CAPTURE_RATE,
} from '@/next.constants.mjs';

init({
// Only run Sentry on Vercel Environment
enabled: SENTRY_ENABLE,
// Tell Sentry where to send events
// Provide Sentry's Secret Key
dsn: SENTRY_DSN,
// Percentage of events to send to Sentry (1% of them) (for performance metrics)
tracesSampleRate: 0.01,
tracesSampleRate: SENTRY_CAPTURE_RATE,
// Percentage of events to send to Sentry (all of them) (for profiling metrics)
// This number is relative to tracesSampleRate - so all traces get profiled
profilesSampleRate: 1.0,
Expand Down

0 comments on commit 7fc2bca

Please sign in to comment.