Files
HealthLog/src/instrumentation.ts
T
Marc-André Bombeck 954701b5f3 feat(observability): every wide event carries the recent event-loop lag
Production showed multi-second windows where every response — routes,
RSC payloads, static assets — waited together while handler durations
stayed under half a second: the process stalled, and nothing
route-shaped could name it. A self-rescheduling tick now records how
late it fires; a tick past the stall threshold emits its own
event_loop_stall background event, and every wide event carries the
recent worst lag so the first request finishing after a stall is
annotated with it. Measured via a timeout chain rather than
perf_hooks.monitorEventLoopDelay — the histogram's sampling timer races
a sibling read-and-reset interval in the same timers phase and the
stall sample can vanish into the reset window.
2026-06-12 20:58:30 +02:00

135 lines
4.0 KiB
TypeScript

import type { Instrumentation } from "next";
export async function register() {
// Only start the worker on the Node.js server runtime (not Edge, not build)
if (process.env.NEXT_RUNTIME === "nodejs") {
// Every Node process watches its own event loop — web-only
// containers included; a stall report from the process serving
// requests is the whole point.
const { startEventLoopLagMonitor } = await import(
"@/lib/observability/event-loop-lag"
);
startEventLoopLagMonitor();
const { shouldRunWorker } = await import("@/lib/process-type");
// Web-only container — the dedicated worker service runs the queues.
if (!shouldRunWorker()) return;
const { WideEventBuilder } = await import("@/lib/logging/event-builder");
const { emitIfSampled } = await import("@/lib/logging/transports");
// Emit startup event
const startupEvt = new WideEventBuilder("background");
startupEvt.setBackground({ task_name: "startup" });
try {
const { startReminderWorker } =
await import("@/lib/jobs/reminder-worker");
await startReminderWorker();
startupEvt.addMeta("reminder_worker", "started");
startupEvt.finish();
emitIfSampled(startupEvt.toJSON());
} catch (err) {
startupEvt.setError(err);
startupEvt.addMeta("reminder_worker", "failed");
startupEvt.finish();
emitIfSampled(startupEvt.toJSON());
// Still log to stderr for container health checks
console.error(
"[instrumentation] CRITICAL: Failed to start reminder worker:",
err,
);
}
}
}
export const onRequestError: Instrumentation.onRequestError = async (
error,
request,
context,
) => {
// Prisma-based settings lookup is not available in edge instrumentation.
if (process.env.NEXT_RUNTIME === "edge") return;
try {
const [{ getGlitchtipSettings }, { sendGlitchtipEvent }] =
await Promise.all([
import("@/lib/monitoring-settings"),
import("@/lib/monitoring/glitchtip"),
]);
const settings = await getGlitchtipSettings();
if (!settings.glitchtipEnabled || !settings.glitchtipDsn) return;
const err =
error instanceof Error
? error
: new Error(
typeof error === "string" ? error : "Unhandled request error",
);
// Skip expected errors from bot scanners (malformed JSON bodies)
if (err instanceof SyntaxError) return;
const userAgentHeader = request.headers["user-agent"];
const userAgent = Array.isArray(userAgentHeader)
? userAgentHeader[0]
: userAgentHeader;
const details = [
`Route: ${context.routePath}`,
`Type: ${context.routeType}`,
`Router: ${context.routerKind}`,
`Method: ${request.method}`,
`Path: ${request.path}`,
context.renderSource ? `Render: ${context.renderSource}` : null,
context.revalidateReason
? `Revalidate: ${context.revalidateReason}`
: null,
]
.filter(Boolean)
.join(" | ");
const message = `${err.message} [${details}]`.slice(0, 1900);
// Try to get request_id from Wide Event context
let requestId: string | undefined;
try {
const { getEvent } = await import("@/lib/logging/context");
requestId = getEvent()?.getRequestId();
} catch {
// Logging not available in this context
}
const delivery = await sendGlitchtipEvent({
dsn: settings.glitchtipDsn,
input: {
environment: settings.glitchtipEnvironment || "production",
message,
stack: err.stack,
level: "error",
type: err.name || "RequestError",
url: request.path,
userAgent,
sourceTag: "healthlog-server",
requestId,
},
});
if (!delivery.ok) {
console.error(
"Global Glitchtip request error reporting failed:",
delivery.status,
delivery.details,
);
}
} catch (reportError) {
console.error(
"Global Glitchtip request error reporting crashed:",
reportError,
);
}
};