@warlock.js/logger 4.6.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/cjs/index.cjs +32 -13
- package/cjs/index.cjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/utils/capture-unhandled-errors.d.mts +36 -13
- package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.mjs +32 -13
- package/esm/utils/capture-unhandled-errors.mjs.map +1 -1
- package/llms-full.txt +547 -531
- package/llms.txt +20 -20
- package/package.json +2 -2
- package/skills/capture-unhandled-errors/SKILL.md +21 -13
- package/skills/flush-logs-on-shutdown/SKILL.md +1 -1
- package/skills/overview/SKILL.md +1 -1
- package/skills/test-logging-code/SKILL.md +8 -0
|
@@ -2,30 +2,49 @@ import { log } from "../logger.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region ../@warlock.js/logger/src/utils/capture-unhandled-errors.ts
|
|
4
4
|
/**
|
|
5
|
+
* Best-effort budget (ms) for draining async channels (file, Sentry, …) before
|
|
6
|
+
* the forced exit. Bounded so a single stuck channel can't hang the process —
|
|
7
|
+
* `process.exit()` skips `beforeExit`, so an `autoFlushOn: ["beforeExit"]`
|
|
8
|
+
* handler would not cover this path.
|
|
9
|
+
*/
|
|
10
|
+
const FLUSH_BUDGET_BEFORE_EXIT = 1e3;
|
|
11
|
+
/**
|
|
5
12
|
* Route Node's process-level failure events through the logger so they land in
|
|
6
|
-
* every configured channel with full stack context
|
|
7
|
-
* `
|
|
8
|
-
* after channels are configured. Pair with `autoFlushOn: ["beforeExit"]` so the
|
|
9
|
-
* final entry survives the process exit that follows an uncaught exception.
|
|
13
|
+
* every configured channel with full stack context, and make an
|
|
14
|
+
* `uncaughtException` loud + visible instead of silently swallowed.
|
|
10
15
|
*
|
|
11
|
-
*
|
|
16
|
+
* Registers one listener for `unhandledRejection` and one for
|
|
17
|
+
* `uncaughtException`; call once at startup after channels are configured.
|
|
12
18
|
*
|
|
13
|
-
* - `uncaughtException` → `log.fatal
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* - `uncaughtException` → `log.fatal`, then (by default) `process.exit(1)`.
|
|
20
|
+
* When no terminal channel has been configured yet — the early-boot window a
|
|
21
|
+
* config file throwing at import time falls into — the stack is also written
|
|
22
|
+
* to `console.error`, standing in for the Node default this listener
|
|
23
|
+
* suppresses, so a fatal boot error is never invisible. A configured
|
|
24
|
+
* `ConsoleLog` already prints it, so the fallback is skipped when a terminal
|
|
25
|
+
* channel exists (no double output).
|
|
26
|
+
* - `unhandledRejection` → `log.error` — a rejected promise is a failure but
|
|
27
|
+
* not necessarily process-ending, so it stays at error and never exits.
|
|
18
28
|
*
|
|
19
29
|
* @example
|
|
20
30
|
* log.configure({ channels: [new ConsoleLog(), new FileLog()] });
|
|
21
|
-
* captureAnyUnhandledRejection();
|
|
31
|
+
* captureAnyUnhandledRejection(); // exits on uncaught
|
|
32
|
+
* captureAnyUnhandledRejection({ exitOnUncaughtException: false }); // dev: log only
|
|
22
33
|
*/
|
|
23
|
-
function captureAnyUnhandledRejection() {
|
|
34
|
+
function captureAnyUnhandledRejection(options = {}) {
|
|
35
|
+
const { exitOnUncaughtException = true } = options;
|
|
24
36
|
process.on("unhandledRejection", (reason) => {
|
|
25
37
|
log.error("app", "unhandledRejection", reason);
|
|
26
38
|
});
|
|
27
39
|
process.on("uncaughtException", (error) => {
|
|
28
|
-
|
|
40
|
+
try {
|
|
41
|
+
log.fatal("app", "uncaughtException", error);
|
|
42
|
+
} catch {}
|
|
43
|
+
if (!log.channels.some((channel) => channel.terminal !== false)) console.error(error);
|
|
44
|
+
if (!exitOnUncaughtException) return;
|
|
45
|
+
Promise.race([log.flush(), new Promise((resolve) => {
|
|
46
|
+
setTimeout(resolve, FLUSH_BUDGET_BEFORE_EXIT).unref?.();
|
|
47
|
+
})]).then(() => process.exit(1), () => process.exit(1));
|
|
29
48
|
});
|
|
30
49
|
}
|
|
31
50
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture-unhandled-errors.mjs","names":[],"sources":["../../../../../../../@warlock.js/logger/src/utils/capture-unhandled-errors.ts"],"sourcesContent":["import { log } from \"../logger\";\r\n\r\n/**\r\n *
|
|
1
|
+
{"version":3,"file":"capture-unhandled-errors.mjs","names":[],"sources":["../../../../../../../@warlock.js/logger/src/utils/capture-unhandled-errors.ts"],"sourcesContent":["import { log } from \"../logger\";\r\n\r\n/**\r\n * Options for {@link captureAnyUnhandledRejection}.\r\n */\r\nexport type CaptureUnhandledOptions = {\r\n /**\r\n * Take the process down with `process.exit(1)` after an `uncaughtException`\r\n * is logged. Defaults to `true` — an uncaught exception leaves the process\r\n * in an undefined state, so Node's own default is to exit non-zero.\r\n *\r\n * Registering an `uncaughtException` listener *suppresses* that default, so a\r\n * listener that only logs turns an unrecoverable crash into a silent\r\n * `exit 0` (which is how a config file throwing at boot looks like \"the\r\n * server just stopped\"). Keep this `true` in production so supervisors\r\n * restart and the failure is never silent. Set it to `false` where the\r\n * process is expected to recover on its own — e.g. a dev server that reloads\r\n * via HMR — to log the exception without exiting.\r\n */\r\n exitOnUncaughtException?: boolean;\r\n};\r\n\r\n/**\r\n * Best-effort budget (ms) for draining async channels (file, Sentry, …) before\r\n * the forced exit. Bounded so a single stuck channel can't hang the process —\r\n * `process.exit()` skips `beforeExit`, so an `autoFlushOn: [\"beforeExit\"]`\r\n * handler would not cover this path.\r\n */\r\nconst FLUSH_BUDGET_BEFORE_EXIT = 1000;\r\n\r\n/**\r\n * Route Node's process-level failure events through the logger so they land in\r\n * every configured channel with full stack context, and make an\r\n * `uncaughtException` loud + visible instead of silently swallowed.\r\n *\r\n * Registers one listener for `unhandledRejection` and one for\r\n * `uncaughtException`; call once at startup after channels are configured.\r\n *\r\n * - `uncaughtException` → `log.fatal`, then (by default) `process.exit(1)`.\r\n * When no terminal channel has been configured yet — the early-boot window a\r\n * config file throwing at import time falls into — the stack is also written\r\n * to `console.error`, standing in for the Node default this listener\r\n * suppresses, so a fatal boot error is never invisible. A configured\r\n * `ConsoleLog` already prints it, so the fallback is skipped when a terminal\r\n * channel exists (no double output).\r\n * - `unhandledRejection` → `log.error` — a rejected promise is a failure but\r\n * not necessarily process-ending, so it stays at error and never exits.\r\n *\r\n * @example\r\n * log.configure({ channels: [new ConsoleLog(), new FileLog()] });\r\n * captureAnyUnhandledRejection(); // exits on uncaught\r\n * captureAnyUnhandledRejection({ exitOnUncaughtException: false }); // dev: log only\r\n */\r\nexport function captureAnyUnhandledRejection(options: CaptureUnhandledOptions = {}) {\r\n const { exitOnUncaughtException = true } = options;\r\n\r\n process.on(\"unhandledRejection\", (reason: any) => {\r\n log.error(\"app\", \"unhandledRejection\", reason);\r\n });\r\n\r\n process.on(\"uncaughtException\", (error) => {\r\n // Route through the logger first so file/Sentry channels capture it. A\r\n // crash handler must never itself throw (that would re-enter Node's\r\n // uncaught path), so the logging call is guarded.\r\n try {\r\n log.fatal(\"app\", \"uncaughtException\", error);\r\n } catch {\r\n // fall through to the stderr fallback + exit below\r\n }\r\n\r\n // Guarantee terminal visibility. `log.fatal` reaches nothing the user can\r\n // see when this fires before any terminal channel is configured — exactly\r\n // the early-boot window a config file throwing at import time falls into,\r\n // which is how a fatal boot error used to masquerade as a silent\r\n // `exit 0`. Stand in for the Node default this listener suppressed, but\r\n // only when no terminal channel already printed it, so a configured\r\n // `ConsoleLog` is never doubled.\r\n const hasTerminalChannel = log.channels.some((channel) => channel.terminal !== false);\r\n\r\n if (!hasTerminalChannel) {\r\n console.error(error);\r\n }\r\n\r\n if (!exitOnUncaughtException) {\r\n return;\r\n }\r\n\r\n // Best-effort, time-bounded flush of async channels, then exit non-zero so\r\n // process supervisors restart and `warlock start` surfaces the failure.\r\n void Promise.race([\r\n log.flush(),\r\n new Promise<void>((resolve) => {\r\n const timer = setTimeout(resolve, FLUSH_BUDGET_BEFORE_EXIT);\r\n timer.unref?.();\r\n }),\r\n ]).then(\r\n () => process.exit(1),\r\n () => process.exit(1),\r\n );\r\n });\r\n}\r\n"],"mappings":";;;;;;;;;AA4BA,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;AAyBjC,SAAgB,6BAA6B,UAAmC,CAAC,GAAG;CAClF,MAAM,EAAE,0BAA0B,SAAS;CAE3C,QAAQ,GAAG,uBAAuB,WAAgB;EAChD,IAAI,MAAM,OAAO,sBAAsB,MAAM;CAC/C,CAAC;CAED,QAAQ,GAAG,sBAAsB,UAAU;EAIzC,IAAI;GACF,IAAI,MAAM,OAAO,qBAAqB,KAAK;EAC7C,QAAQ,CAER;EAWA,IAAI,CAFuB,IAAI,SAAS,MAAM,YAAY,QAAQ,aAAa,KAEzD,GACpB,QAAQ,MAAM,KAAK;EAGrB,IAAI,CAAC,yBACH;EAKF,AAAK,QAAQ,KAAK,CAChB,IAAI,MAAM,GACV,IAAI,SAAe,YAAY;GAE7B,AADc,WAAW,SAAS,wBAC9B,CAAC,CAAC,QAAQ;EAChB,CAAC,CACH,CAAC,CAAC,CAAC,WACK,QAAQ,KAAK,CAAC,SACd,QAAQ,KAAK,CAAC,CACtB;CACF,CAAC;AACH"}
|