diff --git a/.gitignore b/.gitignore index 6765c17b..97792e72 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,6 @@ temp ./traefik/ /user-config/ + +# Sentry Config File +.sentryclirc diff --git a/Dockerfile b/Dockerfile index f997533b..63cda775 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,11 @@ COPY ./next.config.mjs ./next.config.mjs COPY ./public ./public COPY ./tests ./tests +# Sentry +COPY ./sentry.client.config.ts ./sentry.client.config.ts +COPY ./sentry.edge.config.ts ./sentry.edge.config.ts +COPY ./sentry.server.config.ts ./sentry.server.config.ts + RUN npm run build # APP diff --git a/Dockerfile.dev b/Dockerfile.dev index dcbb5847..c3819dd0 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -20,4 +20,9 @@ COPY ./tsconfig.json ./tsconfig.json COPY ./next.config.mjs ./next.config.mjs COPY ./public ./public +# Sentry +COPY ./sentry.client.config.ts ./sentry.client.config.ts +COPY ./sentry.edge.config.ts ./sentry.edge.config.ts +COPY ./sentry.server.config.ts ./sentry.server.config.ts + CMD ["npm", "run", "dev"] diff --git a/next.config.mjs b/next.config.mjs index c8d3b714..b63cabc5 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,3 +1,4 @@ +import { withSentryConfig } from '@sentry/nextjs'; /** @type {import('next').NextConfig} */ const nextConfig = { swcMinify: true, @@ -32,4 +33,20 @@ const nextConfig = { }, }; -export default nextConfig; +export default withSentryConfig( + nextConfig, + { + // https://github.com/getsentry/sentry-webpack-plugin#options + silent: false, + org: 'runtipi', + project: 'runtipi-nextjs', + }, + { + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + widenClientFileUpload: true, + transpileClientSDK: false, + tunnelRoute: '/errors', + hideSourceMaps: false, + disableLogger: true, + }, +); diff --git a/sentry.client.config.ts b/sentry.client.config.ts new file mode 100644 index 00000000..e90d1b07 --- /dev/null +++ b/sentry.client.config.ts @@ -0,0 +1,22 @@ +import * as Sentry from '@sentry/nextjs'; +import { settingsSchema } from '@runtipi/shared/src/schemas/env-schemas'; + +const inputElement = document.getElementById('client-settings') as HTMLInputElement | null; + +if (inputElement) { + try { + // Parse the input value + const parsedSettings = settingsSchema.parse(JSON.parse(inputElement.value)); + + if (parsedSettings.allowErrorMonitoring) { + Sentry.init({ + environment: process.env.NODE_ENV, + dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584', + debug: process.env.NODE_ENV === 'development', + }); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error parsing client settings:', error); + } +} diff --git a/sentry.edge.config.ts b/sentry.edge.config.ts new file mode 100644 index 00000000..634d9767 --- /dev/null +++ b/sentry.edge.config.ts @@ -0,0 +1,16 @@ +// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on). +// The config you add here will be used whenever one of the edge features is loaded. +// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; +import { getConfig } from '@/server/core/TipiConfig'; + +if (getConfig().allowErrorMonitoring) { + Sentry.init({ + environment: getConfig().NODE_ENV, + dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584', + tracesSampleRate: 1, + debug: getConfig().NODE_ENV === 'development', + }); +} diff --git a/sentry.server.config.ts b/sentry.server.config.ts new file mode 100644 index 00000000..65d48aea --- /dev/null +++ b/sentry.server.config.ts @@ -0,0 +1,15 @@ +// 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 * as Sentry from '@sentry/nextjs'; +import { getConfig } from '@/server/core/TipiConfig'; + +if (getConfig().allowErrorMonitoring) { + Sentry.init({ + environment: getConfig().NODE_ENV, + dsn: 'https://7a73d72f886948478b55621e7b92c3c7@o4504242900238336.ingest.sentry.io/4504826587971584', + tracesSampleRate: 1, + debug: getConfig().NODE_ENV === 'development', + }); +} diff --git a/src/app/api/sentry-example-api/route.js b/src/app/api/sentry-example-api/route.js new file mode 100644 index 00000000..f486f3d1 --- /dev/null +++ b/src/app/api/sentry-example-api/route.js @@ -0,0 +1,9 @@ +import { NextResponse } from "next/server"; + +export const dynamic = "force-dynamic"; + +// A faulty API route to test Sentry's error monitoring +export function GET() { + throw new Error("Sentry Example API Route Error"); + return NextResponse.json({ data: "Testing Sentry Error..." }); +} diff --git a/src/app/global-error.jsx b/src/app/global-error.jsx new file mode 100644 index 00000000..bfe3d39b --- /dev/null +++ b/src/app/global-error.jsx @@ -0,0 +1,19 @@ +'use client'; + +import React, { useEffect } from 'react'; +import * as Sentry from '@sentry/nextjs'; +import Error from 'next/error'; + +export default function GlobalError({ error }) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + + + + ); +} diff --git a/src/app/sentry-example-page/page.jsx b/src/app/sentry-example-page/page.jsx new file mode 100644 index 00000000..d38c43ab --- /dev/null +++ b/src/app/sentry-example-page/page.jsx @@ -0,0 +1,86 @@ +"use client"; + +import Head from "next/head"; +import * as Sentry from "@sentry/nextjs"; + +export default function Page() { + return ( +
+ + Sentry Onboarding + + + +
+

+ + + +

+ +

Get started by sending us a sample error:

+ + +

+ Next, look for the error on the{" "} + Issues Page. +

+

+ For more information, see{" "} + + https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +

+
+
+ ); +}