Capturing unhandled errors
captureAnyUnhandledRejection() is a one-call helper that routes Node’s two top-level error events through the logger. Call it once during startup, after your channels are registered.
What it does
Section titled “What it does”Calling the helper attaches two process-level listeners:
| Node event | Forwarded to | Module | Action |
|---|---|---|---|
unhandledRejection | log.error(...) | "app" | "unhandledRejection" |
uncaughtException | log.fatal(...) | "app" | "uncaughtException" |
The split is intentional: an uncaughtException terminates the Node process by default, so it’s semantically fatal. An unhandledRejection is a failure but not necessarily process-ending (depends on Node’s --unhandled-rejections policy and your app’s recovery), so it stays at error. This makes “page only on fatal” alerting clean.
The original error is passed as the message argument, so FileLog captures the full stack trace and JSONFileLog stores the stack as a string[].
import { log, ConsoleLog, FileLog, captureAnyUnhandledRejection,} from "@warlock.js/logger";
log.setChannels([ new ConsoleLog(), new FileLog({ chunk: "daily" }),]);
captureAnyUnhandledRejection();Pair it with flushSync
Section titled “Pair it with flushSync”uncaughtException means Node is about to terminate. Without a synchronous flush the buffered error entry is lost — see Shutdown & flushing.
captureAnyUnhandledRejection();
process.once("beforeExit", () => log.flushSync());Behavior & gotchas
Section titled “Behavior & gotchas”- Call order matters. Register your channels before calling
captureAnyUnhandledRejection(). Listeners forward to the current singleton; there’s no late-binding fallback if channels are added afterward. - Not idempotent. Each call registers another pair of process listeners. Calling it twice double-logs every captured error. Call it exactly once.
- Does not prevent exit. The helper doesn’t replace Node’s default
uncaughtExceptionbehavior — the process still terminates after the handler runs. - Safe when the logger has no channels. The forwarded call silently no-ops; no error is raised.
- No raw stdout writes. The handler routes the failure through
log.erroronly — it never bypasses your channels with a strayconsole.log.
Rolling your own
Section titled “Rolling your own”If you need different routing (send only uncaughtException to the logger, for example), skip the helper and register the listener directly:
import { log } from "@warlock.js/logger";
process.on("uncaughtException", (error) => { log.fatal("app", "uncaughtException", error);});See also
Section titled “See also”- Shutdown & flushing — guarantee the terminal error entry reaches disk before the process exits