mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 16:12:02 +00:00
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
48 lines
1.7 KiB
TypeScript
48 lines
1.7 KiB
TypeScript
import { parseStack } from './stack';
|
|
|
|
/**
|
|
* The debug ID of each loaded bundle file, by scrubbed path.
|
|
*
|
|
* Turbopack (`turbopack.debugIds`) and `sentry-cli sourcemaps inject` (the
|
|
* webpack build in Docker) start every chunk with a line that stores
|
|
* `globalThis[KEY][new Error().stack] = "<debug id>"`. The first frame of that
|
|
* stack is the chunk itself, so its path maps to the id. The error tracker
|
|
* finds the uploaded source map by that id alone.
|
|
*/
|
|
|
|
const KEYS = ['_debugIds', '_sentryDebugIds'] as const;
|
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
|
let cache: { size: number; ids: Map<string, string> } | null = null;
|
|
|
|
function registered(): Array<[string, string]> {
|
|
const g = globalThis as Record<string, unknown>;
|
|
const out: Array<[string, string]> = [];
|
|
for (const key of KEYS) {
|
|
const map = g[key];
|
|
if (!map || typeof map !== 'object') continue;
|
|
for (const [stack, id] of Object.entries(map as Record<string, unknown>)) {
|
|
if (typeof id === 'string' && UUID.test(id)) out.push([stack, id.toLowerCase()]);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Path -> debug ID for every chunk loaded so far. Chunks load lazily, so this is rebuilt when more have registered. */
|
|
export function debugIdsByPath(): Map<string, string> {
|
|
const entries = registered();
|
|
if (cache && cache.size === entries.length) return cache.ids;
|
|
const ids = new Map<string, string>();
|
|
for (const [stack, id] of entries) {
|
|
const first = parseStack(stack).find((frame) => frame.path !== '[other]');
|
|
if (first) ids.set(first.path, id);
|
|
}
|
|
cache = { size: entries.length, ids };
|
|
return ids;
|
|
}
|
|
|
|
/** For tests. */
|
|
export function resetDebugIdCache(): void {
|
|
cache = null;
|
|
}
|