mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 00:14:12 +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
31 lines
1.3 KiB
TypeScript
31 lines
1.3 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { requireAdminAuth } from '@/lib/admin/session';
|
|
import { logger } from '@/lib/logger';
|
|
import { readErrors, summarizeDay, utcDay } from '@/lib/error-reporting/aggregate';
|
|
import { shouldCollectErrors } from '@/lib/error-reporting/settings';
|
|
import { storedDataUnavailableResponse } from '@/lib/stored-data-errors';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
/**
|
|
* GET /api/admin/monitoring: what System > Monitoring shows besides the
|
|
* config keys (those come from /api/admin/config): whether errors are being
|
|
* counted and today's counts.
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const auth = await requireAdminAuth(request);
|
|
if ('error' in auth) return auth.error;
|
|
|
|
const collecting = await shouldCollectErrors();
|
|
const today = utcDay();
|
|
const errorsToday = summarizeDay(await readErrors(), today);
|
|
return NextResponse.json({ collecting, day: today, errorsToday }, { headers: { 'Cache-Control': 'no-store' } });
|
|
} catch (err) {
|
|
logger.error('monitoring GET error', { error: err instanceof Error ? err.message : 'unknown' });
|
|
const unavailable = storedDataUnavailableResponse(err);
|
|
if (unavailable) return unavailable;
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|