Add extended diagnostics logging

- Electron crash handlers (render-process-gone, child-process-gone,
  unresponsive/responsive, process warnings) with a circuit-breaker
  auto-reload for renderer crashes
- Renderer error capture (window.onerror, unhandledrejection, React
  ErrorBoundary) forwarded to the main log via a one-way IPC channel
- Memory-pressure heartbeat measured against the V8 heap_size_limit
- Gated DEBUG log level (RD_DEBUG) and an in-memory ring of recent
  WARN/ERROR lines, exposed via the /errors endpoint and support bundle
- Disk-error classification (ENOSPC etc.) on download failures and
  integrity-check pass/fail logging
This commit is contained in:
Sucukdeluxe
2026-06-07 17:00:06 +02:00
parent 2ececf699a
commit 468df99142
17 changed files with 582 additions and 7 deletions
+29 -2
View File
@@ -1,7 +1,24 @@
import fs from "node:fs";
import { logTimestamp } from "./log-timestamp";
import { recordRecentError } from "./error-ring";
import path from "node:path";
export function isDebugFlagEnabled(value: string | undefined): boolean {
if (!value) {
return false;
}
return /^(1|true|yes|on)$/i.test(value.trim());
}
// Read once at startup. Enabling verbose DEBUG logging on the (unattended) server
// is a deliberate support action that requires a restart — the runtime-toggleable
// channel is the trace log, not this.
const DEBUG_ENABLED = isDebugFlagEnabled(process.env.RD_DEBUG);
export function isDebugLoggingEnabled(): boolean {
return DEBUG_ENABLED;
}
let logFilePath = path.resolve(process.cwd(), "rd_downloader.log");
let fallbackLogFilePath: string | null = null;
const LOG_FLUSH_INTERVAL_MS = 120;
@@ -204,12 +221,19 @@ function ensureExitHook(): void {
process.once("exit", flushSyncPending);
}
function write(level: "INFO" | "WARN" | "ERROR", message: string): void {
function write(level: "DEBUG" | "INFO" | "WARN" | "ERROR", message: string): void {
ensureExitHook();
const line = `${logTimestamp()} [${level}] ${message}\n`;
const ts = logTimestamp();
const line = `${ts} [${level}] ${message}\n`;
pendingLines.push(line);
pendingChars += line.length;
// Single chokepoint: every WARN/ERROR also lands in the in-memory ring so
// "what failed recently" is answerable even after the file rotates.
if (level === "ERROR" || level === "WARN") {
recordRecentError(level, message, ts);
}
for (const listener of logListeners) {
try { listener(line); } catch { }
}
@@ -230,6 +254,9 @@ function write(level: "INFO" | "WARN" | "ERROR", message: string): void {
}
export const logger = {
// Gated to a no-op when RD_DEBUG is unset so verbose call sites cost nothing
// (no formatting, no allocation) in the normal/production path.
debug: DEBUG_ENABLED ? (msg: string): void => write("DEBUG", msg) : (_msg: string): void => {},
info: (msg: string): void => write("INFO", msg),
warn: (msg: string): void => write("WARN", msg),
error: (msg: string): void => write("ERROR", msg)