@warlock.js/logger 4.5.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
package/llms.txt
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# Warlock Logger
|
|
2
|
-
|
|
3
|
-
> Package: `@warlock.js/logger`
|
|
4
|
-
|
|
5
|
-
> A powerful logging system for messages and errors in nodejs.
|
|
6
|
-
|
|
7
|
-
## Skills
|
|
8
|
-
|
|
9
|
-
- [capture-unhandled-errors](@warlock.js/logger/capture-unhandled-errors/SKILL.md):
|
|
10
|
-
- [configure-logger](@warlock.js/logger/configure-logger/SKILL.md): Register channels via log.addChannel / log.setChannels / log.configure({channels, autoFlushOn, redact, minLevel}) at boot. Triggers: `log.configure`, `log.addChannel`, `log.setChannels`, `Logger`, `autoFlushOn`, `disableAutoFlush`; "wire channels at startup", "branch logger by NODE_ENV", "isolate a library's logger", "replace channel list"; typical import `import { log, Logger, ConsoleLog, FileLog } from "@warlock.js/logger"`. Skip: channel picks — `@warlock.js/logger/pick-log-channel/SKILL.md`; flushing — `@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`; redaction — `@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`; competing libs `winston.createLogger`, `pino`.
|
|
11
|
-
- [filter-log-entries](@warlock.js/logger/filter-log-entries/SKILL.md):
|
|
12
|
-
- [flush-logs-on-shutdown](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md):
|
|
13
|
-
- [logger-basics](@warlock.js/logger/logger-basics/SKILL.md):
|
|
14
|
-
- [overview](@warlock.js/logger/overview/SKILL.md):
|
|
15
|
-
- [pick-log-channel](@warlock.js/logger/pick-log-channel/SKILL.md):
|
|
16
|
-
- [redact-sensitive-log-fields](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md): Strip secrets from log output — two-layer additive redaction via log.configure({redact: {paths}}) (logger floor) + per-channel redact (more paths on top). Dotted glob paths (*, **). Triggers: `redact`, `paths`, `censor`, `log.setRedact`, `applyRedact`; "redact passwords in logs", "strip tokens from log output", "hide authorization headers", "scrub PII before logging"; typical import `import { log } from "@warlock.js/logger"`. Skip: filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; competing libs `pino.redact`, `fast-redact`.
|
|
17
|
-
- [ship-logs-to-sentry](@warlock.js/logger/ship-logs-to-sentry/SKILL.md):
|
|
18
|
-
- [test-logging-code](@warlock.js/logger/test-logging-code/SKILL.md): Test code that touches the logger — silence globally via log.setChannels([]) in setupFiles, assert specific log lines via a capturing LogChannel subclass (prefer it over vi.spyOn — it asserts on delivered entries, not just method calls, and isolates the shared singleton cleanly). Triggers: `log.setChannels`, `LogChannel`, `LoggingData`, `Logger`, `log.channels`; "silence logger in vitest", "assert a log line was emitted", "capture log output in tests", "test code that logs"; typical import `import { log, Logger, LogChannel, type LoggingData } from "@warlock.js/logger"`. Skip: custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; competing `vi.spyOn(console)`, `jest.spyOn`.
|
|
19
|
-
- [use-log-helpers](@warlock.js/logger/use-log-helpers/SKILL.md): Two DX shortcuts on every Logger — log.assert(condition, module, action, message, context?) logs an error when condition is falsy (free on the happy path), log.timer(module, action) returns an end-function emitting an info entry with measured duration. Triggers: `log.assert`, `log.timer`, `durationMs`; "assert an invariant via logger", "measure how long an operation took", "time a request", "log operation duration"; typical import `import { log } from "@warlock.js/logger"`. Skip: basics — `@warlock.js/logger/logger-basics/SKILL.md`; filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; competing `console.assert`, `console.time`, `console.timeEnd`, `perf_hooks.performance.now`.
|
|
20
|
-
- [write-custom-log-channel](@warlock.js/logger/write-custom-log-channel/SKILL.md):
|
|
1
|
+
# Warlock Logger
|
|
2
|
+
|
|
3
|
+
> Package: `@warlock.js/logger`
|
|
4
|
+
|
|
5
|
+
> A powerful logging system for messages and errors in nodejs.
|
|
6
|
+
|
|
7
|
+
## Skills
|
|
8
|
+
|
|
9
|
+
- [capture-unhandled-errors](@warlock.js/logger/capture-unhandled-errors/SKILL.md): (no description)
|
|
10
|
+
- [configure-logger](@warlock.js/logger/configure-logger/SKILL.md): Register channels via log.addChannel / log.setChannels / log.configure({channels, autoFlushOn, redact, minLevel}) at boot. Triggers: `log.configure`, `log.addChannel`, `log.setChannels`, `Logger`, `autoFlushOn`, `disableAutoFlush`; "wire channels at startup", "branch logger by NODE_ENV", "isolate a library's logger", "replace channel list"; typical import `import { log, Logger, ConsoleLog, FileLog } from "@warlock.js/logger"`. Skip: channel picks — `@warlock.js/logger/pick-log-channel/SKILL.md`; flushing — `@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`; redaction — `@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`; competing libs `winston.createLogger`, `pino`.
|
|
11
|
+
- [filter-log-entries](@warlock.js/logger/filter-log-entries/SKILL.md): (no description)
|
|
12
|
+
- [flush-logs-on-shutdown](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md): (no description)
|
|
13
|
+
- [logger-basics](@warlock.js/logger/logger-basics/SKILL.md): (no description)
|
|
14
|
+
- [overview](@warlock.js/logger/overview/SKILL.md): (no description)
|
|
15
|
+
- [pick-log-channel](@warlock.js/logger/pick-log-channel/SKILL.md): (no description)
|
|
16
|
+
- [redact-sensitive-log-fields](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md): Strip secrets from log output — two-layer additive redaction via log.configure({redact: {paths}}) (logger floor) + per-channel redact (more paths on top). Dotted glob paths (*, **). Triggers: `redact`, `paths`, `censor`, `log.setRedact`, `applyRedact`; "redact passwords in logs", "strip tokens from log output", "hide authorization headers", "scrub PII before logging"; typical import `import { log } from "@warlock.js/logger"`. Skip: filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; competing libs `pino.redact`, `fast-redact`.
|
|
17
|
+
- [ship-logs-to-sentry](@warlock.js/logger/ship-logs-to-sentry/SKILL.md): (no description)
|
|
18
|
+
- [test-logging-code](@warlock.js/logger/test-logging-code/SKILL.md): Test code that touches the logger — silence globally via log.setChannels([]) in setupFiles, assert specific log lines via a capturing LogChannel subclass (prefer it over vi.spyOn — it asserts on delivered entries, not just method calls, and isolates the shared singleton cleanly). Triggers: `log.setChannels`, `LogChannel`, `LoggingData`, `Logger`, `log.channels`; "silence logger in vitest", "assert a log line was emitted", "capture log output in tests", "test code that logs"; typical import `import { log, Logger, LogChannel, type LoggingData } from "@warlock.js/logger"`. Skip: custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; competing `vi.spyOn(console)`, `jest.spyOn`.
|
|
19
|
+
- [use-log-helpers](@warlock.js/logger/use-log-helpers/SKILL.md): Two DX shortcuts on every Logger — log.assert(condition, module, action, message, context?) logs an error when condition is falsy (free on the happy path), log.timer(module, action) returns an end-function emitting an info entry with measured duration. Triggers: `log.assert`, `log.timer`, `durationMs`; "assert an invariant via logger", "measure how long an operation took", "time a request", "log operation duration"; typical import `import { log } from "@warlock.js/logger"`. Skip: basics — `@warlock.js/logger/logger-basics/SKILL.md`; filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; competing `console.assert`, `console.time`, `console.timeEnd`, `perf_hooks.performance.now`.
|
|
20
|
+
- [write-custom-log-channel](@warlock.js/logger/write-custom-log-channel/SKILL.md): (no description)
|
package/package.json
CHANGED
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@mongez/copper": "^2.1.2",
|
|
19
19
|
"@mongez/reinforcements": "^3.3.0",
|
|
20
|
-
"@warlock.js/fs": "4.
|
|
20
|
+
"@warlock.js/fs": "4.6.1",
|
|
21
21
|
"dayjs": "^1.11.9",
|
|
22
22
|
"safe-stable-stringify": "^2.5.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@sentry/node": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
|
26
26
|
},
|
|
27
|
-
"version": "4.
|
|
27
|
+
"version": "4.6.1",
|
|
28
28
|
"main": "./cjs/index.cjs",
|
|
29
29
|
"module": "./esm/index.mjs",
|
|
30
30
|
"types": "./esm/index.d.mts",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: capture-unhandled-errors
|
|
3
|
-
description: 'captureAnyUnhandledRejection() installs process.on(''unhandledRejection'') → log.error and process.on(''uncaughtException'') → log.fatal so process-level failures land in your
|
|
3
|
+
description: 'captureAnyUnhandledRejection() installs process.on(''unhandledRejection'') → log.error and process.on(''uncaughtException'') → log.fatal + process.exit(1) so process-level failures land in your channels and a fatal crash is never silently swallowed into exit 0. Triggers: `captureAnyUnhandledRejection`, `exitOnUncaughtException`, `unhandledRejection`, `uncaughtException`, `log.error`, `log.fatal`; "log unhandled promise rejections", "catch uncaught exceptions to a file", "record crashes before exit", "server exits 0 with no error", "silent exit / production server stopped", "global error handler with logger"; typical import `import { captureAnyUnhandledRejection, log } from "@warlock.js/logger"`. Skip: flushing — `@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`; filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; competing `Sentry.init`, `@sentry/node`; native `process.on(''unhandledRejection'')`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Error capture — routing Node's unhandled errors through the logger
|
|
7
7
|
|
|
8
|
-
`captureAnyUnhandledRejection()` installs two process-level listeners so crashes are logged
|
|
8
|
+
`captureAnyUnhandledRejection()` installs two process-level listeners so crashes are logged — and, for an `uncaughtException`, made loud and terminal — instead of being silently swallowed.
|
|
9
9
|
|
|
10
10
|
## What it does
|
|
11
11
|
|
|
@@ -16,10 +16,19 @@ captureAnyUnhandledRejection();
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Registers:
|
|
19
|
-
- `process.on("unhandledRejection", reason => log.error("app", "unhandledRejection", reason))`
|
|
20
|
-
- `process.on("uncaughtException", error => log.fatal("app", "uncaughtException", error))`
|
|
19
|
+
- `process.on("unhandledRejection", reason => log.error("app", "unhandledRejection", reason))` — logged; the process is kept alive.
|
|
20
|
+
- `process.on("uncaughtException", error => log.fatal("app", "uncaughtException", error))` — logged, then `process.exit(1)` (by default).
|
|
21
21
|
|
|
22
|
-
The split is intentional: an `uncaughtException`
|
|
22
|
+
The split is intentional: an `uncaughtException` leaves the process in an undefined state, so it's semantically `fatal` and takes the process down. An `unhandledRejection` is a failure but not always process-ending (depends on Node's `--unhandled-rejections` policy and your app's recovery), so it stays at `error` and never exits. This makes "page on fatal" alerting clean — only true crashes ring the pager.
|
|
23
|
+
|
|
24
|
+
## Why it exits (and why that matters)
|
|
25
|
+
|
|
26
|
+
Registering *any* `uncaughtException` listener **suppresses** Node's default "print the stack + exit non-zero." A listener that only logs therefore turns an unrecoverable crash into a silent `exit 0` — which is exactly how a config file that throws at boot can look like "the server started, then just stopped," with no error printed. So the handler restores the contract:
|
|
27
|
+
|
|
28
|
+
- **Exits non-zero** after logging (`process.exit(1)`), following a best-effort, time-bounded `log.flush()` so buffered `FileLog` / `SentryLog` entries drain first. Opt out with `captureAnyUnhandledRejection({ exitOnUncaughtException: false })` where the process is expected to recover on its own (a dev server reloading via HMR).
|
|
29
|
+
- **Falls back to `console.error`** when no terminal channel is configured yet — the early-boot window, before `log.configure(...)`, where `log.fatal` has nowhere visible to go. When a `ConsoleLog` is present it already prints the entry, so the fallback is skipped (no double output).
|
|
30
|
+
|
|
31
|
+
> The framework wires this for you in `bootstrap()` as `captureAnyUnhandledRejection({ exitOnUncaughtException: Application.isProduction })` — production crashes loudly and non-zero, the dev server logs-and-continues so HMR can recover.
|
|
23
32
|
|
|
24
33
|
## When to call it
|
|
25
34
|
|
|
@@ -41,15 +50,11 @@ log.configure({
|
|
|
41
50
|
captureAnyUnhandledRejection();
|
|
42
51
|
```
|
|
43
52
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
Without a flush on exit, here's what happens on a crash:
|
|
53
|
+
## Flushing on the crash path
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
2. Node exits.
|
|
50
|
-
3. Buffer is never flushed. **The error that killed your app is lost.**
|
|
55
|
+
The `uncaughtException` handler runs a best-effort, time-bounded `log.flush()` **before** its own `process.exit(1)`, so buffered `FileLog` / `SentryLog` entries drain even though `process.exit()` skips `beforeExit`. You don't need `autoFlushOn: ["beforeExit"]` for the fatal entry to survive — the handler already drains.
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
`"beforeExit"` in `autoFlushOn` is still worth setting for the *other* exit routes (a natural drain when the event loop empties on its own). For signal-driven shutdown (`SIGINT` / `SIGTERM`), include those signals in `autoFlushOn`. See [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md).
|
|
53
58
|
|
|
54
59
|
## Idempotency — don't call it twice
|
|
55
60
|
|
|
@@ -57,7 +62,8 @@ Calling `captureAnyUnhandledRejection()` a second time registers a second pair o
|
|
|
57
62
|
|
|
58
63
|
## What it does **not** do
|
|
59
64
|
|
|
60
|
-
- **Does not swallow
|
|
65
|
+
- **Does not swallow a fatal error.** After an `uncaughtException` it records the error, then exits non-zero itself — restoring Node's own default, which merely registering the listener would otherwise suppress. Pass `{ exitOnUncaughtException: false }` only when the process is meant to survive (e.g. HMR).
|
|
66
|
+
- **Does not exit on an `unhandledRejection`.** That path only logs (at `error`); the process keeps running. Set Node's `--unhandled-rejections=throw` if you want a rejection to escalate to an `uncaughtException`.
|
|
61
67
|
- **Does not install Node's `--unhandled-rejections` policy.** That's a Node flag; set it in your launch script if you want strict mode.
|
|
62
68
|
- **Does not hook `SIGTERM` / `SIGINT`** — use `enableAutoFlush` for signal flushes.
|
|
63
69
|
- **Does not filter.** Every rejection is logged at `error` and every uncaught exception at `fatal`, both with `module: "app"`. Filter per-channel if some noise slips in.
|
|
@@ -93,6 +99,8 @@ it("routes unhandled rejections to the logger", async () => {
|
|
|
93
99
|
});
|
|
94
100
|
```
|
|
95
101
|
|
|
102
|
+
Testing the **`uncaughtException`** path additionally trips `process.exit(1)`, so stub it (`vi.spyOn(process, "exit").mockImplementation(() => undefined as never)`) or pass `{ exitOnUncaughtException: false }` — otherwise the emitted exception tears the test runner down. See [`@warlock.js/logger/test-logging-code/SKILL.md`](@warlock.js/logger/test-logging-code/SKILL.md).
|
|
103
|
+
|
|
96
104
|
## Module + action the capture uses
|
|
97
105
|
|
|
98
106
|
Both listeners log with:
|
|
@@ -99,7 +99,7 @@ log.flushSync();
|
|
|
99
99
|
|
|
100
100
|
## Unhandled errors
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
The `uncaughtException` path in [`captureAnyUnhandledRejection()`](@warlock.js/logger/capture-unhandled-errors/SKILL.md) already runs a best-effort, time-bounded `log.flush()` before its own `process.exit(1)`, so the fatal entry drains without extra wiring (and `process.exit()` skips `beforeExit`, so a `beforeExit` handler would not fire on that path anyway). Still set `autoFlushOn` for the *other* shutdown routes — `"SIGINT"` / `"SIGTERM"` and a natural `"beforeExit"` — so those don't lose the last buffered batch.
|
|
103
103
|
|
|
104
104
|
```ts
|
|
105
105
|
log.configure({
|
package/skills/overview/SKILL.md
CHANGED
|
@@ -56,7 +56,7 @@ Drop entries before they cost anything. Logger-wide `setMinLevel("info")` is the
|
|
|
56
56
|
Buffered channels need explicit drain. `log.flushSync()` (sync) for file channels — also wired by `enableAutoFlush(['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK', 'SIGUSR2', 'beforeExit'])`. `await log.flush()` (async) for network/async channels like `SentryLog` — the only path that can await an HTTPS round-trip on a graceful shutdown.
|
|
57
57
|
|
|
58
58
|
#### [`capture-unhandled-errors`](@warlock.js/logger/capture-unhandled-errors/SKILL.md)
|
|
59
|
-
`captureAnyUnhandledRejection()` hooks `unhandledRejection` (→ `log.error("app", ...)
|
|
59
|
+
`captureAnyUnhandledRejection()` hooks `unhandledRejection` (→ `log.error("app", ...)`, process kept alive) and `uncaughtException` (→ `log.fatal("app", ...)` then `process.exit(1)` — restoring the non-zero exit the listener would otherwise suppress, so a fatal crash never becomes a silent `exit 0`; opt out with `{ exitOnUncaughtException: false }`). It flushes before exiting and falls back to `console.error` when no terminal channel is set. One call at startup.
|
|
60
60
|
|
|
61
61
|
### Ergonomics + testing
|
|
62
62
|
|
|
@@ -166,4 +166,12 @@ captureAnyUnhandledRejection();
|
|
|
166
166
|
process.emit("unhandledRejection", new Error("test"), Promise.resolve());
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
The `uncaughtException` path additionally calls `process.exit(1)`, so stub it (or pass `{ exitOnUncaughtException: false }`) before emitting — otherwise the emitted exception tears the test runner down:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
173
|
+
captureAnyUnhandledRejection();
|
|
174
|
+
process.emit("uncaughtException", new Error("test"), "uncaughtException");
|
|
175
|
+
```
|
|
176
|
+
|
|
169
177
|
See [`@warlock.js/logger/capture-unhandled-errors/SKILL.md`](@warlock.js/logger/capture-unhandled-errors/SKILL.md) for a full example.
|