web: report server and browser errors to Bugsink
This commit is contained in:
10 files changed
+1685
-12
No files matched your search
+18
-1
@@ -1,4 +1,5 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
import { withSentryConfig } from "@sentry/nextjs/config";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
@@ -53,4 +54,20 @@ const nextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
// Errors are reported to Bugsink (bugsink.neuf.no), which speaks Sentry's
|
||||
// protocol. The build uploads source maps there when SENTRY_AUTH_TOKEN is
|
||||
// set, and removes them from the output afterwards
|
||||
export default withSentryConfig(nextConfig, {
|
||||
sentryUrl: "https://bugsink.neuf.no/",
|
||||
// Bugsink has no organisations; sentry-cli insists on a value
|
||||
org: "bugsinkhasnoorgs",
|
||||
project: "neuf-www",
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
silent: !process.env.CI,
|
||||
telemetry: false,
|
||||
sourcemaps: { deleteSourcemapsAfterUpload: true },
|
||||
// the hook it asks for is for tracing navigations, which is off
|
||||
suppressOnRouterTransitionStartWarning: true,
|
||||
// Bugsink only takes errors: leave the tracing code out of the bundles
|
||||
bundleSizeOptimizations: { excludeDebugStatements: true, excludeTracing: true },
|
||||
});
|
||||
Generated
+1597
-7
File diff suppressed because it is too large.
Load diff
@@ -21,6 +21,8 @@
|
||||
"@graphql-codegen/cli": "^7.4.6",
|
||||
"@graphql-codegen/client-preset": "^6.2.3",
|
||||
"@parcel/watcher": "^2.6.0",
|
||||
"@sentry/browser": "^11.7.0",
|
||||
"@sentry/nextjs": "^11.7.0",
|
||||
"@sindresorhus/slugify": "^3.0.1",
|
||||
"@urql/next": "^2.0.1",
|
||||
"date-fns": "^4.4.0",
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
import { useEffect } from "react";
|
||||
import { PageHeader } from "@/components/general/PageHeader";
|
||||
import { isChunkLoadError } from "@/lib/chunkError";
|
||||
import { reportError } from "@/lib/reportError";
|
||||
|
||||
const RELOAD_KEY = "chunk-error-reload";
|
||||
const RELOAD_COOLDOWN_MS = 10_000;
|
||||
|
||||
function ErrorPage({ error }: { error: Error & { digest?: string } }) {
|
||||
useEffect(() => {
|
||||
reportError(error);
|
||||
}, [error]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isChunkLoadError(error)) return;
|
||||
// No reload loop if the chunk is gone for good
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { reportError } from "@/lib/reportError";
|
||||
|
||||
// Errors that no React error boundary catches
|
||||
window.addEventListener("error", (event) => {
|
||||
reportError(event.error ?? event.message);
|
||||
});
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
reportError(event.reason);
|
||||
});
|
||||
@@ -6,6 +6,10 @@ vi.mock("@/lib/internalRevalidate", () => ({
|
||||
setInternalRevalidateSecret: vi.fn(),
|
||||
}));
|
||||
|
||||
// the real SDK is not under test, and pulls in all of Node's instrumentation
|
||||
vi.mock("@sentry/nextjs", () => ({ captureRequestError: vi.fn() }));
|
||||
vi.mock("./sentry.server.config", () => ({}));
|
||||
|
||||
import { register } from "./instrumentation.ts";
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// the real SDK is not under test, and pulls in all of Node's instrumentation
|
||||
vi.mock("@sentry/nextjs", () => ({ captureRequestError: vi.fn() }));
|
||||
vi.mock("./sentry.server.config", () => ({}));
|
||||
|
||||
import { internalRevalidateSecret } from "@/lib/internalRevalidate";
|
||||
import { register } from "./instrumentation.ts";
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { osloDay } from "@/lib/date";
|
||||
import {
|
||||
purgeOwnCache,
|
||||
@@ -84,11 +85,20 @@ function keepCacheCurrent() {
|
||||
void check();
|
||||
}
|
||||
|
||||
export function register() {
|
||||
// only in the running production server: not during `next build`, in
|
||||
// `next dev` (nothing is kept between requests there) or on the edge
|
||||
// Reports errors thrown while rendering or handling a request to Bugsink
|
||||
export const onRequestError = Sentry.captureRequestError;
|
||||
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME !== "nodejs") return;
|
||||
|
||||
keepCacheCurrentInProduction();
|
||||
await import("./sentry.server.config");
|
||||
}
|
||||
|
||||
function keepCacheCurrentInProduction() {
|
||||
// only in the running production server: not during `next build` or in
|
||||
// `next dev` (nothing is kept between requests there)
|
||||
if (
|
||||
process.env.NEXT_RUNTIME !== "nodejs" ||
|
||||
process.env.NODE_ENV !== "production" ||
|
||||
process.env.NEXT_PHASE === "phase-production-build"
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Sends an error from the browser to Bugsink. The SDK is only downloaded
|
||||
// once there is something to report, so it costs nothing on a normal visit
|
||||
let sdk: Promise<typeof import("@sentry/browser")> | undefined;
|
||||
|
||||
export function reportError(error: unknown) {
|
||||
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
// unset (as in local builds) leaves error reporting off
|
||||
if (!dsn || process.env.NODE_ENV !== "production") return;
|
||||
|
||||
sdk ??= import("@sentry/browser").then((Sentry) => {
|
||||
Sentry.init({
|
||||
dsn,
|
||||
// Bugsink takes errors only
|
||||
tracesSampleRate: 0,
|
||||
// instrumentation-client.ts listens for uncaught errors itself, from
|
||||
// before the SDK is loaded
|
||||
integrations: (defaults) =>
|
||||
defaults.filter((integration) => integration.name !== "GlobalHandlers"),
|
||||
});
|
||||
return Sentry;
|
||||
});
|
||||
void sdk.then((Sentry) => Sentry.captureException(error));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
Sentry.init({
|
||||
// unset (as in local builds) leaves error reporting off
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
enabled: process.env.NODE_ENV === "production",
|
||||
// Bugsink takes errors only
|
||||
tracesSampleRate: 0,
|
||||
});
|
||||
Reference in new issue
Block a user