Files
webmail/components/error/error-reporting-root.tsx
T
Linus Rath cba1109444 feat: report browser and server errors to an error tracker the admin sets
Admins had logs and nothing else: client errors only reached the
browser console. An admin can now set a Sentry-protocol address (DSN)
for Bugsink, GlitchTip or Sentry under System > Monitoring, or with
`ERROR_REPORTING_DSN`, `ERROR_REPORTING_DSN_FILE`,
`ERROR_REPORTING_SAMPLE_RATE` and `ERROR_REPORTING_ENVIRONMENT`.
Bulwark Lite reads the same from `errorReporting` in policy.json.
Everything stays off while no address is set.

- browsers report errors from the error boundaries, window.onerror,
  unhandled rejections and failed JMAP requests (method and error type
  only), with route and JMAP method breadcrumbs and the chunks' debug IDs
- the server reports what Next hands to onRequestError and what routes
  log with logger.error
- every report is scrubbed first: quoted text, addresses, URLs, file
  names, tokens and the rest after the first line are removed, and the
  server scrubs what browsers post to /api/errors again
- reports are deduplicated and rate-limited per session and process
- errors are counted per day for "errors today" on the admin page
- startup messages and audit entries go through the logger, so
  `LOG_FORMAT=json` covers them
2026-11-01 02:50:19 +01:00

31 lines
1006 B
TypeScript

"use client";
import { useEffect } from "react";
import { usePathname } from "next/navigation";
import { fetchConfig } from "@/hooks/use-config";
import { installGlobalHandlers, recordNavigation } from "@/lib/error-reporting/client";
/**
* Starts browser error reporting: window.onerror and unhandled rejections,
* and a breadcrumb per route change (the route's area only, never the URL).
* Whether anything is sent is decided by the config (/api/config, or Lite's
* policy.json); until it arrives, reports wait, and with reporting off they
* are dropped. Renders nothing.
*/
export function ErrorReportingRoot() {
const pathname = usePathname();
useEffect(() => {
installGlobalHandlers();
// Cached after the first fetch; this only makes sure it happens on pages
// that don't read the config themselves (admin, setup).
void fetchConfig().catch(() => undefined);
}, []);
useEffect(() => {
if (pathname) recordNavigation(pathname);
}, [pathname]);
return null;
}