@warlock.js/logger 4.1.15 → 4.2.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 +30 -0
- package/LICENSE +21 -0
- package/README.md +146 -145
- package/cjs/index.cjs +276 -11
- package/cjs/index.cjs.map +1 -1
- package/esm/channels/console-log.d.mts +1 -1
- package/esm/channels/console-log.d.mts.map +1 -1
- package/esm/channels/console-log.mjs +4 -1
- package/esm/channels/console-log.mjs.map +1 -1
- package/esm/channels/file-log.d.mts +13 -3
- package/esm/channels/file-log.d.mts.map +1 -1
- package/esm/channels/file-log.mjs +15 -1
- package/esm/channels/file-log.mjs.map +1 -1
- package/esm/channels/index.mjs +1 -0
- package/esm/channels/json-file-log.d.mts +1 -1
- package/esm/channels/json-file-log.d.mts.map +1 -1
- package/esm/channels/json-file-log.mjs +1 -1
- package/esm/channels/json-file-log.mjs.map +1 -1
- package/esm/channels/sentry-log.d.mts +182 -0
- package/esm/channels/sentry-log.d.mts.map +1 -0
- package/esm/channels/sentry-log.mjs +202 -0
- package/esm/channels/sentry-log.mjs.map +1 -0
- package/esm/index.d.mts +2 -1
- package/esm/index.mjs +2 -1
- package/esm/log-channel.d.mts +9 -1
- package/esm/log-channel.d.mts.map +1 -1
- package/esm/log-channel.mjs +1 -1
- package/esm/log-channel.mjs.map +1 -1
- package/esm/logger.d.mts +32 -1
- package/esm/logger.d.mts.map +1 -1
- package/esm/logger.mjs +44 -2
- package/esm/logger.mjs.map +1 -1
- package/esm/redact/redact.d.mts +1 -1
- package/esm/redact/redact.d.mts.map +1 -1
- package/esm/redact/redact.mjs +1 -1
- package/esm/redact/redact.mjs.map +1 -1
- package/esm/types.d.mts +19 -3
- package/esm/types.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.d.mts +9 -1
- package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.mjs +10 -2
- package/esm/utils/capture-unhandled-errors.mjs.map +1 -1
- package/esm/utils/clear-message.d.mts +1 -1
- package/esm/utils/clear-message.d.mts.map +1 -1
- package/esm/utils/clear-message.mjs +1 -1
- package/esm/utils/clear-message.mjs.map +1 -1
- package/esm/utils/safe-json-stringify.d.mts +1 -1
- package/esm/utils/safe-json-stringify.d.mts.map +1 -1
- package/esm/utils/safe-json-stringify.mjs +1 -1
- package/esm/utils/safe-json-stringify.mjs.map +1 -1
- package/llms-full.txt +1497 -1296
- package/llms.txt +20 -19
- package/package.json +5 -2
- package/skills/capture-unhandled-errors/SKILL.md +103 -103
- package/skills/filter-log-entries/SKILL.md +120 -120
- package/skills/flush-logs-on-shutdown/SKILL.md +117 -91
- package/skills/logger-basics/SKILL.md +88 -85
- package/skills/overview/SKILL.md +90 -86
- package/skills/pick-log-channel/SKILL.md +155 -139
- package/skills/ship-logs-to-sentry/SKILL.md +118 -0
- package/skills/write-custom-log-channel/SKILL.md +190 -160
|
@@ -1,91 +1,117 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: flush-logs-on-shutdown
|
|
3
|
-
description: 'Drain buffered channels before exit — log.flushSync() or log.configure({autoFlushOn: [''SIGINT'', ''SIGTERM'', ''beforeExit'']}) installs handlers that re-raise the signal. Triggers: `log.flushSync`, `autoFlushOn`, `enableAutoFlush`, `disableAutoFlush`, `SIGINT`, `SIGTERM`, `beforeExit`; "drain logs before exit", "wire SIGTERM for container shutdown", "my logs never showed after a crash", "graceful shutdown logging"; typical import `import { log, FileLog } from "@warlock.js/logger"`. Skip: error capture — `@warlock.js/logger/capture-unhandled-errors/SKILL.md`; custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; competing `pino.final`, `winston.end`; native `process.on(''exit'')`.'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Lifecycle — flushing buffered channels before exit
|
|
7
|
-
|
|
8
|
-
`FileLog` and `JSONFileLog` buffer entries in memory. A process that exits without draining loses the buffer.
|
|
9
|
-
|
|
10
|
-
## The easy way — `autoFlushOn`
|
|
11
|
-
|
|
12
|
-
Tell the logger which process events should trigger a flush. It installs the handlers for you.
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
log.configure({
|
|
16
|
-
channels: [new ConsoleLog(), new FileLog({ chunk: "daily" })],
|
|
17
|
-
autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
|
|
18
|
-
});
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### What each event does
|
|
22
|
-
|
|
23
|
-
| Event | Behavior |
|
|
24
|
-
|---|---|
|
|
25
|
-
| `SIGINT` / `SIGTERM` / `SIGHUP` / `SIGBREAK` / `SIGUSR2` | Flush → remove this handler → re-raise the signal so Node's default exit code runs (e.g. 130 for SIGINT). |
|
|
26
|
-
| `beforeExit` | Flush in place. Node continues its natural exit. |
|
|
27
|
-
|
|
28
|
-
### Default recommendation
|
|
29
|
-
|
|
30
|
-
`["SIGINT", "SIGTERM", "beforeExit"]` covers:
|
|
31
|
-
- Local `Ctrl+C` (SIGINT)
|
|
32
|
-
- Container orchestrators (`docker stop`, Kubernetes sending SIGTERM)
|
|
33
|
-
- Natural exit (Node finished all work)
|
|
34
|
-
|
|
35
|
-
Add `"SIGHUP"` if you care about terminal disconnects. Add `"SIGUSR2"` if you use nodemon or pm2 restart.
|
|
36
|
-
|
|
37
|
-
### Idempotency
|
|
38
|
-
|
|
39
|
-
Calling `enableAutoFlush` twice **replaces** previous handlers — it does not stack. `disableAutoFlush()` removes every handler this logger instance registered; safe to call when nothing is registered.
|
|
40
|
-
|
|
41
|
-
## The manual way — your own handler
|
|
42
|
-
|
|
43
|
-
Use this when you need async work (close an HTTP server, drain a queue) **before** flushing:
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
async function gracefulShutdown() {
|
|
47
|
-
await httpServer.close();
|
|
48
|
-
await queue.drain();
|
|
49
|
-
log.flushSync(); // still sync — guarantees disk write before exit
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
process.once("SIGINT", gracefulShutdown);
|
|
54
|
-
process.once("SIGTERM", gracefulShutdown);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**If you go manual for a signal, skip it in `autoFlushOn`** — otherwise both handlers fire and ours re-raises the signal mid-way through your async work.
|
|
58
|
-
|
|
59
|
-
##
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
1
|
+
---
|
|
2
|
+
name: flush-logs-on-shutdown
|
|
3
|
+
description: 'Drain buffered channels before exit — log.flushSync() or log.configure({autoFlushOn: [''SIGINT'', ''SIGTERM'', ''beforeExit'']}) installs handlers that re-raise the signal. Triggers: `log.flush`, `log.flushSync`, `autoFlushOn`, `enableAutoFlush`, `disableAutoFlush`, `SIGINT`, `SIGTERM`, `beforeExit`; "drain logs before exit", "await log.flush() before process.exit", "drain async or network channels on shutdown", "wire SIGTERM for container shutdown", "my logs never showed after a crash", "graceful shutdown logging"; typical import `import { log, FileLog } from "@warlock.js/logger"`. Skip: error capture — `@warlock.js/logger/capture-unhandled-errors/SKILL.md`; custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; competing `pino.final`, `winston.end`; native `process.on(''exit'')`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lifecycle — flushing buffered channels before exit
|
|
7
|
+
|
|
8
|
+
`FileLog` and `JSONFileLog` buffer entries in memory. A process that exits without draining loses the buffer.
|
|
9
|
+
|
|
10
|
+
## The easy way — `autoFlushOn`
|
|
11
|
+
|
|
12
|
+
Tell the logger which process events should trigger a flush. It installs the handlers for you.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
log.configure({
|
|
16
|
+
channels: [new ConsoleLog(), new FileLog({ chunk: "daily" })],
|
|
17
|
+
autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### What each event does
|
|
22
|
+
|
|
23
|
+
| Event | Behavior |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `SIGINT` / `SIGTERM` / `SIGHUP` / `SIGBREAK` / `SIGUSR2` | Flush → remove this handler → re-raise the signal so Node's default exit code runs (e.g. 130 for SIGINT). |
|
|
26
|
+
| `beforeExit` | Flush in place. Node continues its natural exit. |
|
|
27
|
+
|
|
28
|
+
### Default recommendation
|
|
29
|
+
|
|
30
|
+
`["SIGINT", "SIGTERM", "beforeExit"]` covers:
|
|
31
|
+
- Local `Ctrl+C` (SIGINT)
|
|
32
|
+
- Container orchestrators (`docker stop`, Kubernetes sending SIGTERM)
|
|
33
|
+
- Natural exit (Node finished all work)
|
|
34
|
+
|
|
35
|
+
Add `"SIGHUP"` if you care about terminal disconnects. Add `"SIGUSR2"` if you use nodemon or pm2 restart.
|
|
36
|
+
|
|
37
|
+
### Idempotency
|
|
38
|
+
|
|
39
|
+
Calling `enableAutoFlush` twice **replaces** previous handlers — it does not stack. `disableAutoFlush()` removes every handler this logger instance registered; safe to call when nothing is registered.
|
|
40
|
+
|
|
41
|
+
## The manual way — your own handler
|
|
42
|
+
|
|
43
|
+
Use this when you need async work (close an HTTP server, drain a queue) **before** flushing:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
async function gracefulShutdown() {
|
|
47
|
+
await httpServer.close();
|
|
48
|
+
await queue.drain();
|
|
49
|
+
log.flushSync(); // still sync — guarantees disk write before exit
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
process.once("SIGINT", gracefulShutdown);
|
|
54
|
+
process.once("SIGTERM", gracefulShutdown);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**If you go manual for a signal, skip it in `autoFlushOn`** — otherwise both handlers fire and ours re-raises the signal mid-way through your async work.
|
|
58
|
+
|
|
59
|
+
## Async drain — `log.flush()`
|
|
60
|
+
|
|
61
|
+
`flushSync()` blocks the event loop with synchronous I/O. That's correct for the file channels and required inside the handlers `autoFlushOn` installs (a re-raised signal kills the process before any promise could settle). But a channel whose delivery is **async** — a network transport, an async disk write — can't drain synchronously. For those, `await` the async sibling on a graceful path you control:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
async function gracefulShutdown() {
|
|
65
|
+
await httpServer.close();
|
|
66
|
+
await log.flush(); // awaits every channel's async flush() to completion
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
process.once("SIGTERM", gracefulShutdown);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`log.flush()` fans out to every channel that implements `flush()` and awaits them together (`Promise.allSettled`). Each channel is isolated — one channel's flush rejecting neither aborts the others nor escapes as an unhandled rejection. Channels without `flush()` are skipped.
|
|
74
|
+
|
|
75
|
+
| | `flushSync()` | `flush()` |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| I/O | synchronous — blocks the loop | asynchronous — awaited |
|
|
78
|
+
| Safe in a re-raising signal handler | yes | no — the signal exits before the promise settles |
|
|
79
|
+
| Used by `autoFlushOn` | yes | no |
|
|
80
|
+
| Reach for it when | file channels, last-resort durability | network/async channels, manual `await` before `process.exit` |
|
|
81
|
+
| `FileLog` / `JSONFileLog` | ✓ | ✓ (async write) |
|
|
82
|
+
|
|
83
|
+
`autoFlushOn` always uses `flushSync()` — signal re-raising can't wait on a promise. If a channel needs async delivery on shutdown, drive `await log.flush()` from your own handler and leave that signal out of `autoFlushOn`.
|
|
84
|
+
|
|
85
|
+
## What `flushSync()` actually does
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
log.flushSync();
|
|
89
|
+
// For every registered channel:
|
|
90
|
+
// if (channel.flushSync) channel.flushSync();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- Synchronous I/O — blocks the event loop.
|
|
94
|
+
- Channels without `flushSync` (e.g. `ConsoleLog` — nothing to flush) are skipped silently.
|
|
95
|
+
- Works with and without `groupBy` on `FileLog` / `JSONFileLog`.
|
|
96
|
+
- No-op if every channel's buffer is empty.
|
|
97
|
+
|
|
98
|
+
`ConsoleLog` has no `flushSync` — it writes synchronously on every entry. `FileLog` and `JSONFileLog` both implement it.
|
|
99
|
+
|
|
100
|
+
## Unhandled errors
|
|
101
|
+
|
|
102
|
+
If you use [`captureAnyUnhandledRejection()`](@warlock.js/logger/capture-unhandled-errors/SKILL.md), **include `"beforeExit"` in `autoFlushOn`**. Otherwise a crash logs the error into the buffer, then the process exits before the 5-second flush interval fires.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
log.configure({
|
|
106
|
+
channels: [new FileLog({ levels: ["error"] })],
|
|
107
|
+
autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
captureAnyUnhandledRejection();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## What NOT to do
|
|
114
|
+
|
|
115
|
+
- **Don't `await` inside a signal handler you wrote yourself and then call `flushSync`** — if an async step rejects, you skip the flush. Wrap in `try { await x } finally { log.flushSync(); process.exit(1); }`.
|
|
116
|
+
- **Don't call `process.exit()` inside `autoFlushOn` handlers** — signal handlers here already re-raise the signal. Forcing an exit breaks exit codes.
|
|
117
|
+
- **Don't rely on the 5-second flush interval for shutdown safety.** It's a throughput optimization, not a durability guarantee.
|
|
@@ -1,85 +1,88 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: logger-basics
|
|
3
|
-
description: 'Start with @warlock.js/logger — the log singleton,
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Log with channels
|
|
7
|
-
|
|
8
|
-
Multi-channel structured logger for Node.js.
|
|
9
|
-
|
|
10
|
-
> This skill is the logger **map** — read it first, then load the specific skill for the task.
|
|
11
|
-
|
|
12
|
-
## Install
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
yarn add @warlock.js/logger
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Foundations
|
|
19
|
-
|
|
20
|
-
The 11 things that are true in every logger use:
|
|
21
|
-
|
|
22
|
-
1. **Public API is the `log` singleton** (`import { log } from "@warlock.js/logger"`). It's a `Logger` instance — call `log.info(...)`, `log.configure(...)`, etc. No callable `log(data)` form.
|
|
23
|
-
2. **The singleton starts with zero channels.** Nothing is written until at least one channel is registered via `addChannel`, `setChannels`, or `configure`.
|
|
24
|
-
3. **Custom instances:** `new Logger()` gives an isolated logger with the identical API. Almost always you want the singleton — reach for the class only when you need an isolated channel set (libraries, test sandboxes).
|
|
25
|
-
4. **
|
|
26
|
-
5. **Channels can be filtered two ways:** a `levels` array (whitelist) and a `filter` predicate (custom logic). Both run on every entry. See [`@warlock.js/logger/filter-log-entries/SKILL.md`](@warlock.js/logger/filter-log-entries/SKILL.md).
|
|
27
|
-
6. **Logger-wide minimum severity** is available via `log.setMinLevel("info")` (or `configure({ minLevel })`). Entries below the rank are dropped before fan-out — cheaper than per-channel filters.
|
|
28
|
-
7. **Redaction** is two-layer additive: `configure({ redact })` sets the logger floor; `new XxxChannel({ redact: { paths: [...] } })` adds more paths on top. Channels can never remove paths from the logger floor. See [`@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md).
|
|
29
|
-
8. **`FileLog` and `JSONFileLog` buffer in memory.** They flush when `maxMessagesToWrite` (default `100`) is hit, when 5 seconds have elapsed since the last write, or when `flushSync()` is called. See [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md).
|
|
30
|
-
9. **Non-terminal channels receive ANSI-stripped messages.** `Logger.log` shallow-clones the entry per non-terminal channel before stripping, so later terminal channels still get the colored original.
|
|
31
|
-
10. **`JSONFileLog.extension` is always `"json"`.** The option is ignored for this channel.
|
|
32
|
-
11. **`captureAnyUnhandledRejection()` registers process listeners.** Call it once at startup, after channels are registered. Calling it twice installs duplicate listeners. See [`@warlock.js/logger/capture-unhandled-errors/SKILL.md`](@warlock.js/logger/capture-unhandled-errors/SKILL.md).
|
|
33
|
-
|
|
34
|
-
## Minimal startup example
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import { log, ConsoleLog, FileLog } from "@warlock.js/logger";
|
|
38
|
-
|
|
39
|
-
log.configure({
|
|
40
|
-
channels: [
|
|
41
|
-
new ConsoleLog(),
|
|
42
|
-
new FileLog({ chunk: "daily", storagePath: "./storage/logs" }),
|
|
43
|
-
],
|
|
44
|
-
autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
await log.info("users", "register", "New user created");
|
|
48
|
-
await log.error("payments", "charge", new Error("Card declined"));
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## The
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
log.debug("module", "action", "verbose detail"); // dev-only diagnostics
|
|
55
|
-
log.info("module", "action", "neutral event"); // user-visible event
|
|
56
|
-
log.warn("module", "action", "something off"); // recoverable concern
|
|
57
|
-
log.error("module", "action", error); // failure
|
|
58
|
-
log.success("module", "action", "operation done"); // explicit success
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- Don't
|
|
83
|
-
- Don't
|
|
84
|
-
- Don't
|
|
85
|
-
- Don't
|
|
1
|
+
---
|
|
2
|
+
name: logger-basics
|
|
3
|
+
description: 'Start with @warlock.js/logger — the log singleton, six levels (debug / info / warn / error / success / fatal), channel fan-out, foundations. Triggers: `log`, `Logger`, `log.info`, `log.error`, `log.fatal`, `log.debug`, `log.warn`, `log.success`, `ConsoleLog`, `FileLog`, `JSONFileLog`; "how do I log in node", "warlock logger basics", "which logger skill do I need"; typical import `import { log, ConsoleLog, FileLog } from "@warlock.js/logger"`. Skip: channel picks — `@warlock.js/logger/pick-log-channel/SKILL.md`; setup — `@warlock.js/logger/configure-logger/SKILL.md`; competing libs `winston`, `pino`, `bunyan`, `log4js`, `signale`; native `console.log`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Log with channels
|
|
7
|
+
|
|
8
|
+
Multi-channel structured logger for Node.js. Four built-in channels (`ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`), an abstract `LogChannel` base for custom sinks, six severity levels, and a safe shutdown path via `Logger.enableAutoFlush(events)` plus async `log.flush()` for network channels.
|
|
9
|
+
|
|
10
|
+
> This skill is the logger **map** — read it first, then load the specific skill for the task.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
yarn add @warlock.js/logger
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Foundations
|
|
19
|
+
|
|
20
|
+
The 11 things that are true in every logger use:
|
|
21
|
+
|
|
22
|
+
1. **Public API is the `log` singleton** (`import { log } from "@warlock.js/logger"`). It's a `Logger` instance — call `log.info(...)`, `log.configure(...)`, etc. No callable `log(data)` form.
|
|
23
|
+
2. **The singleton starts with zero channels.** Nothing is written until at least one channel is registered via `addChannel`, `setChannels`, or `configure`.
|
|
24
|
+
3. **Custom instances:** `new Logger()` gives an isolated logger with the identical API. Almost always you want the singleton — reach for the class only when you need an isolated channel set (libraries, test sandboxes).
|
|
25
|
+
4. **Six levels, closed union:** `"debug" | "info" | "warn" | "error" | "success" | "fatal"`. `fatal` ranks strictly above `error` — use it for unrecoverable failures where the app is going down (failed bootstrap, `uncaughtException`). There are no custom levels.
|
|
26
|
+
5. **Channels can be filtered two ways:** a `levels` array (whitelist) and a `filter` predicate (custom logic). Both run on every entry. See [`@warlock.js/logger/filter-log-entries/SKILL.md`](@warlock.js/logger/filter-log-entries/SKILL.md).
|
|
27
|
+
6. **Logger-wide minimum severity** is available via `log.setMinLevel("info")` (or `configure({ minLevel })`). Entries below the rank are dropped before fan-out — cheaper than per-channel filters.
|
|
28
|
+
7. **Redaction** is two-layer additive: `configure({ redact })` sets the logger floor; `new XxxChannel({ redact: { paths: [...] } })` adds more paths on top. Channels can never remove paths from the logger floor. See [`@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md).
|
|
29
|
+
8. **`FileLog` and `JSONFileLog` buffer in memory.** They flush when `maxMessagesToWrite` (default `100`) is hit, when 5 seconds have elapsed since the last write, or when `flushSync()` is called. See [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md).
|
|
30
|
+
9. **Non-terminal channels receive ANSI-stripped messages.** `Logger.log` shallow-clones the entry per non-terminal channel before stripping, so later terminal channels still get the colored original.
|
|
31
|
+
10. **`JSONFileLog.extension` is always `"json"`.** The option is ignored for this channel.
|
|
32
|
+
11. **`captureAnyUnhandledRejection()` registers process listeners.** Call it once at startup, after channels are registered. Calling it twice installs duplicate listeners. See [`@warlock.js/logger/capture-unhandled-errors/SKILL.md`](@warlock.js/logger/capture-unhandled-errors/SKILL.md).
|
|
33
|
+
|
|
34
|
+
## Minimal startup example
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { log, ConsoleLog, FileLog } from "@warlock.js/logger";
|
|
38
|
+
|
|
39
|
+
log.configure({
|
|
40
|
+
channels: [
|
|
41
|
+
new ConsoleLog(),
|
|
42
|
+
new FileLog({ chunk: "daily", storagePath: "./storage/logs" }),
|
|
43
|
+
],
|
|
44
|
+
autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await log.info("users", "register", "New user created");
|
|
48
|
+
await log.error("payments", "charge", new Error("Card declined"));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The six levels
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
log.debug("module", "action", "verbose detail"); // dev-only diagnostics
|
|
55
|
+
log.info("module", "action", "neutral event"); // user-visible event
|
|
56
|
+
log.warn("module", "action", "something off"); // recoverable concern
|
|
57
|
+
log.error("module", "action", error); // handled failure, app continues
|
|
58
|
+
log.success("module", "action", "operation done"); // explicit success
|
|
59
|
+
log.fatal("module", "action", error); // unrecoverable, app is going down
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`fatal` is purely informational — it does NOT auto-flush or exit. The caller decides whether to `await log.flush()` and `process.exit(...)`. See [`@warlock.js/logger/capture-unhandled-errors/SKILL.md`](@warlock.js/logger/capture-unhandled-errors/SKILL.md) for the `uncaughtException` → `fatal` routing.
|
|
63
|
+
|
|
64
|
+
Every call signature is the same — `module`, `action`, `message`, optional `context`. `message` can be a string, object, or `Error` instance (file channels capture the stack).
|
|
65
|
+
|
|
66
|
+
## Pick a skill
|
|
67
|
+
|
|
68
|
+
| If the task is about… | Load |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| Picking a channel — what each built-in does, when to use which | [`@warlock.js/logger/pick-log-channel/SKILL.md`](@warlock.js/logger/pick-log-channel/SKILL.md) |
|
|
71
|
+
| Startup — registering channels, environment-based setup, the `configure` method | [`@warlock.js/logger/configure-logger/SKILL.md`](@warlock.js/logger/configure-logger/SKILL.md) |
|
|
72
|
+
| Filtering log output (`levels`, `filter`, per-channel routing, `minLevel`) | [`@warlock.js/logger/filter-log-entries/SKILL.md`](@warlock.js/logger/filter-log-entries/SKILL.md) |
|
|
73
|
+
| Graceful shutdown — `flushSync`, `autoFlushOn`, signal behavior | [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md) |
|
|
74
|
+
| Extending `LogChannel` to build a custom sink (Slack, database, HTTP) | [`@warlock.js/logger/write-custom-log-channel/SKILL.md`](@warlock.js/logger/write-custom-log-channel/SKILL.md) |
|
|
75
|
+
| Routing Node's `unhandledRejection` / `uncaughtException` through the logger | [`@warlock.js/logger/capture-unhandled-errors/SKILL.md`](@warlock.js/logger/capture-unhandled-errors/SKILL.md) |
|
|
76
|
+
| `log.assert(...)` and `log.timer(...)` shorthand helpers | [`@warlock.js/logger/use-log-helpers/SKILL.md`](@warlock.js/logger/use-log-helpers/SKILL.md) |
|
|
77
|
+
| Redacting secrets — logger floor + additive channel paths | [`@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md) |
|
|
78
|
+
| Tests that assert on log output, or code under test that logs | [`@warlock.js/logger/test-logging-code/SKILL.md`](@warlock.js/logger/test-logging-code/SKILL.md) |
|
|
79
|
+
|
|
80
|
+
## Things NOT to do
|
|
81
|
+
|
|
82
|
+
- Don't try `log(module, action, message)` or `log({...})` directly — `log` is a `Logger` instance, not a function. Use `log.info(...)`, `log.error(...)`, etc., or the explicit `log.log({ type, module, action, message })` for the data-object form.
|
|
83
|
+
- Don't set `extension` on `JSONFileLog` — it's hardcoded to `"json"` and your value is silently ignored.
|
|
84
|
+
- Don't register multiple `FileLog` instances with the same `name` in the same `storagePath` — the lookup via `log.channel("file")` returns only one, and they'll fight over the same file.
|
|
85
|
+
- Don't mix `autoFlushOn: ["SIGINT"]` with your own `process.on("SIGINT", ...)` handler — both fire, and ours re-raises mid-way through your async work.
|
|
86
|
+
- Don't `await log.info(...)` expecting the write to be on disk — `FileLog` buffers. Call `log.flushSync()` (or rely on `autoFlushOn`) before the process exits.
|
|
87
|
+
- Don't call `captureAnyUnhandledRejection()` more than once — it re-registers listeners every call and your rejections get logged N times.
|
|
88
|
+
- Don't shadow the import in local code: `for (const log of logEntries) { ... }` will hide the singleton inside that block. Rename loop variables (`entry`, `record`) when working with logger imports.
|
package/skills/overview/SKILL.md
CHANGED
|
@@ -1,86 +1,90 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: overview
|
|
3
|
-
description: 'Front-door orientation for `@warlock.js/logger` — structured channel-based logging with
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# `@warlock.js/logger` — overview
|
|
7
|
-
|
|
8
|
-
Structured logging for Node.
|
|
9
|
-
|
|
10
|
-
Ships standalone — `@warlock.js/core` is not required. Drop it into any Node project.
|
|
11
|
-
|
|
12
|
-
## When to reach for it
|
|
13
|
-
|
|
14
|
-
- Building a Node service that needs **structured** logs (key-value pairs, not bare strings) and you want them to land in multiple destinations (console for dev, JSON file for prod, third-party sink for audits) without rewriting the call sites.
|
|
15
|
-
- You'd reach for **pino** or **winston** but want a smaller surface that's already wired into Warlock conventions (`module / action / message` shape, redaction floor, signal flush built-in).
|
|
16
|
-
- Your team agrees that **`console.log` doesn't survive contact with production** — you need filtering, level routing, channel-specific sinks, and a redaction story before secrets leak into Slack/Datadog.
|
|
17
|
-
|
|
18
|
-
Skip if your code is a throwaway script where `console.log` is genuinely fine — there's no value in adding a dependency for one-off logs.
|
|
19
|
-
|
|
20
|
-
## The mental model in one paragraph
|
|
21
|
-
|
|
22
|
-
You write `log.info("auth", "login", "user signed in", { userId })`. The logger fans that single entry out to every registered channel (`ConsoleLog`, `FileLog`, `JSONFileLog`, or your custom subclass). Each channel decides whether to emit it (per-level whitelist, per-channel filter predicate, logger-wide minimum severity). Redaction runs once at the logger level and can be extended per channel — never relaxed. Buffered channels (file + JSON file) drain on flush
|
|
23
|
-
|
|
24
|
-
## Skills index
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
### Foundations
|
|
29
|
-
|
|
30
|
-
#### [`logger-basics`](@warlock.js/logger/logger-basics/SKILL.md)
|
|
31
|
-
Start here. The `log` singleton, the
|
|
32
|
-
|
|
33
|
-
#### [`configure-logger`](@warlock.js/logger/configure-logger/SKILL.md)
|
|
34
|
-
Wire channels at boot — `log.addChannel`, `log.setChannels`, `log.configure({ channels, autoFlushOn, redact, minLevel })`. Branch on `NODE_ENV`, replace the channel list, isolate a library's logger from the host singleton.
|
|
35
|
-
|
|
36
|
-
### Channels
|
|
37
|
-
|
|
38
|
-
#### [`pick-log-channel`](@warlock.js/logger/pick-log-channel/SKILL.md)
|
|
39
|
-
Pick one of the
|
|
40
|
-
|
|
41
|
-
#### [`
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
#### [`
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
#### [`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
#### [`
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
#### [`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
86
|
-
|
|
1
|
+
---
|
|
2
|
+
name: overview
|
|
3
|
+
description: 'Front-door orientation for `@warlock.js/logger` — structured channel-based logging with six severity levels (debug / info / warn / error / success / fatal), PII redaction floor, buffered file/JSON channels, optional SentryLog forwarding, async log.flush() + signal-flush on shutdown, ergonomic helpers (timer, assert). Standalone — no `@warlock.js/core` required. TRIGGER when: code imports anything from `@warlock.js/logger`; user asks "what does @warlock.js/logger do", "compare with pino / winston / bunyan", "structured logging for Node", "which logger should I use", "how do channels work"; package.json adds `@warlock.js/logger`. Skip: specific task already known — load the matching task skill directly (`logger-basics`, `configure-logger`, `pick-log-channel`, `write-custom-log-channel`, `ship-logs-to-sentry`, `redact-sensitive-log-fields`, `filter-log-entries`, `flush-logs-on-shutdown`, `capture-unhandled-errors`, `use-log-helpers`, `test-logging-code`); plain `console.log` in throwaway scripts.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `@warlock.js/logger` — overview
|
|
7
|
+
|
|
8
|
+
Structured logging for Node. Six severity levels (with `fatal` strictly above `error`), a singleton plus a `Logger` class, channel-based fan-out (one entry → many sinks), PII redaction as a floor that channels can extend, buffered file writes with signal-triggered flush on shutdown, an awaitable async `log.flush()` for network/async channels, an optional Sentry channel, and a couple of ergonomic helpers (`timer`, `assert`) that turn boilerplate into one-liners.
|
|
9
|
+
|
|
10
|
+
Ships standalone — `@warlock.js/core` is not required. Drop it into any Node project.
|
|
11
|
+
|
|
12
|
+
## When to reach for it
|
|
13
|
+
|
|
14
|
+
- Building a Node service that needs **structured** logs (key-value pairs, not bare strings) and you want them to land in multiple destinations (console for dev, JSON file for prod, third-party sink for audits) without rewriting the call sites.
|
|
15
|
+
- You'd reach for **pino** or **winston** but want a smaller surface that's already wired into Warlock conventions (`module / action / message` shape, redaction floor, signal flush built-in).
|
|
16
|
+
- Your team agrees that **`console.log` doesn't survive contact with production** — you need filtering, level routing, channel-specific sinks, and a redaction story before secrets leak into Slack/Datadog.
|
|
17
|
+
|
|
18
|
+
Skip if your code is a throwaway script where `console.log` is genuinely fine — there's no value in adding a dependency for one-off logs.
|
|
19
|
+
|
|
20
|
+
## The mental model in one paragraph
|
|
21
|
+
|
|
22
|
+
You write `log.info("auth", "login", "user signed in", { userId })`. The logger fans that single entry out to every registered channel (`ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, or your custom subclass). Each channel decides whether to emit it (per-level whitelist, per-channel filter predicate, logger-wide minimum severity). Redaction runs once at the logger level and can be extended per channel — never relaxed. Buffered channels (file + JSON file) drain on flush — synchronously via `log.flushSync()` / `enableAutoFlush(['SIGINT', 'SIGTERM', 'beforeExit'])`, or asynchronously via `await log.flush()` (the only path that works for network channels like `SentryLog`). That's the whole package.
|
|
23
|
+
|
|
24
|
+
## Skills index
|
|
25
|
+
|
|
26
|
+
Eleven task skills cover everything. Load the one that matches your job — most callers only ever need `logger-basics` + `configure-logger` + `pick-log-channel`.
|
|
27
|
+
|
|
28
|
+
### Foundations
|
|
29
|
+
|
|
30
|
+
#### [`logger-basics`](@warlock.js/logger/logger-basics/SKILL.md)
|
|
31
|
+
Start here. The `log` singleton, the six levels (`debug` / `info` / `warn` / `error` / `success` / `fatal`), how fan-out works, the `module / action / message / context` shape every entry carries.
|
|
32
|
+
|
|
33
|
+
#### [`configure-logger`](@warlock.js/logger/configure-logger/SKILL.md)
|
|
34
|
+
Wire channels at boot — `log.addChannel`, `log.setChannels`, `log.configure({ channels, autoFlushOn, redact, minLevel })`. Branch on `NODE_ENV`, replace the channel list, isolate a library's logger from the host singleton.
|
|
35
|
+
|
|
36
|
+
### Channels
|
|
37
|
+
|
|
38
|
+
#### [`pick-log-channel`](@warlock.js/logger/pick-log-channel/SKILL.md)
|
|
39
|
+
Pick one of the four built-ins: `ConsoleLog` (terminal, colored), `FileLog` (plain `.log` on disk with rotation), `JSONFileLog` (structured JSON for aggregators — Datadog, Loki, ELK), `SentryLog` (errors + breadcrumbs to Sentry; `@sentry/node` is an optional peer).
|
|
40
|
+
|
|
41
|
+
#### [`ship-logs-to-sentry`](@warlock.js/logger/ship-logs-to-sentry/SKILL.md)
|
|
42
|
+
The `SentryLog` channel in depth — event-vs-breadcrumb level mapping, dual init modes (reuse an existing client or pass `options`), draining via `Sentry.flush(timeout)`, behavior when the optional peer isn't installed.
|
|
43
|
+
|
|
44
|
+
#### [`write-custom-log-channel`](@warlock.js/logger/write-custom-log-channel/SKILL.md)
|
|
45
|
+
Extend `LogChannel<Options>` for sinks the built-ins don't cover — Slack, HTTP endpoint, in-memory buffer, database. The lazy `init()` lifecycle (`setTimeout(0)`), the `terminal: true/false` ANSI-stripping behavior, and the difference between `flush()` (async, network) and `flushSync()` (sync, files) are subtle — read this skill before subclassing.
|
|
46
|
+
|
|
47
|
+
### Production concerns
|
|
48
|
+
|
|
49
|
+
#### [`redact-sensitive-log-fields`](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md)
|
|
50
|
+
Strip secrets before they reach a sink. Logger-wide `setRedact({ paths, censor })` is the security floor; per-channel `redact` configs add paths (never remove). Dotted-glob paths (`*`, `**`); censor as string or function `(value, path) => any`.
|
|
51
|
+
|
|
52
|
+
#### [`filter-log-entries`](@warlock.js/logger/filter-log-entries/SKILL.md)
|
|
53
|
+
Drop entries before they cost anything. Logger-wide `setMinLevel("info")` is the fast path; per-channel `levels` array + `filter` predicate for fine control.
|
|
54
|
+
|
|
55
|
+
#### [`flush-logs-on-shutdown`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md)
|
|
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
|
+
|
|
58
|
+
#### [`capture-unhandled-errors`](@warlock.js/logger/capture-unhandled-errors/SKILL.md)
|
|
59
|
+
`captureAnyUnhandledRejection()` hooks `unhandledRejection` (→ `log.error("app", ...)`) and `uncaughtException` (→ `log.fatal("app", ...)` — Node terminates by default, so it's semantically fatal). One call at startup, pair with `autoFlushOn: ['beforeExit']` to land the entry on disk.
|
|
60
|
+
|
|
61
|
+
### Ergonomics + testing
|
|
62
|
+
|
|
63
|
+
#### [`use-log-helpers`](@warlock.js/logger/use-log-helpers/SKILL.md)
|
|
64
|
+
Two shortcuts every `Logger` exposes: `log.assert(condition, module, action, message, context?)` logs an error only when the condition is falsy (free on the happy path); `log.timer(module, action)` returns an end-function that emits `info` with a measured `durationMs`.
|
|
65
|
+
|
|
66
|
+
#### [`test-logging-code`](@warlock.js/logger/test-logging-code/SKILL.md)
|
|
67
|
+
Silence the logger globally in tests via `log.setChannels([])` in `setupFiles`. Assert specific entries with a capturing `LogChannel` subclass — it proves an entry was actually delivered through the pipeline (filters, redaction), not merely that a method was called, and it isolates cleanly by swapping `log.channels`.
|
|
68
|
+
|
|
69
|
+
## Built-in channels at a glance
|
|
70
|
+
|
|
71
|
+
| Channel | Sink | `terminal` | Buffered |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| `ConsoleLog` | `process.stdout` | `true` (colors kept) | no |
|
|
74
|
+
| `FileLog` | `.log` files | `false` (ANSI stripped) | yes (5s timer or 100-entry buffer) |
|
|
75
|
+
| `JSONFileLog` | `.json` files | `false` (ANSI stripped) | yes (same buffering) |
|
|
76
|
+
| `SentryLog` | Sentry events + breadcrumbs | `false` | via the Sentry SDK's own transport (`@sentry/node` is an optional peer) |
|
|
77
|
+
|
|
78
|
+
`terminal: true` is the flag that decides whether the channel sees raw colored messages or stripped plain text. Custom channels: pick `true` if you write to a terminal, `false` for anything else.
|
|
79
|
+
|
|
80
|
+
## What this package deliberately doesn't do
|
|
81
|
+
|
|
82
|
+
- **Distributed tracing.** Use OpenTelemetry. Logger gets you structured local logs with `module / action`; trace correlation is a different problem.
|
|
83
|
+
- **Log aggregator integrations beyond Sentry.** `SentryLog` is bundled (with `@sentry/node` as an optional peer). For Datadog, Loki, ELK, etc., either write a custom channel that POSTs to the aggregator or use `JSONFileLog` + a sidecar (fluentbit, vector, promtail).
|
|
84
|
+
- **Pretty-printing of arbitrary objects.** `ConsoleLog` has a `showContext` flag that runs `util.inspect` on the context object; for richer formatting, use `JSONFileLog` and view the file through your favorite viewer.
|
|
85
|
+
- **Log analysis.** Querying / aggregating / alerting is on the sink side (Loki, ELK, Datadog).
|
|
86
|
+
|
|
87
|
+
## See also
|
|
88
|
+
|
|
89
|
+
- [`@warlock.js/core/warlock-conventions`](@warlock.js/core/warlock-conventions/SKILL.md) — the parent framework's conventions; logger is one of its foundation packages and ships transitively when you install core.
|
|
90
|
+
- When synced via agent-kit, this `overview/SKILL.md` is flattened to the front-door skill `.claude/skills/warlock-js-logger-overview/` — every cross-link above uses the `@warlock.js/logger/<skill>/SKILL.md` name form so it survives that flattening.
|