@warlock.js/logger 4.0.174 → 4.1.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.
Files changed (135) hide show
  1. package/README.md +145 -422
  2. package/cjs/index.cjs +1003 -0
  3. package/cjs/index.cjs.map +1 -0
  4. package/esm/channels/console-log.d.mts +40 -0
  5. package/esm/channels/console-log.d.mts.map +1 -0
  6. package/esm/channels/console-log.mjs +51 -0
  7. package/esm/channels/console-log.mjs.map +1 -0
  8. package/esm/channels/file-log.d.mts +194 -0
  9. package/esm/channels/file-log.d.mts.map +1 -0
  10. package/esm/channels/file-log.mjs +267 -0
  11. package/esm/channels/file-log.mjs.map +1 -0
  12. package/esm/channels/index.mjs +5 -0
  13. package/esm/channels/json-file-log.d.mts +33 -0
  14. package/esm/channels/json-file-log.d.mts.map +1 -0
  15. package/esm/channels/json-file-log.mjs +137 -0
  16. package/esm/channels/json-file-log.mjs.map +1 -0
  17. package/esm/index.d.mts +11 -0
  18. package/esm/index.mjs +13 -0
  19. package/esm/log-channel.d.mts +78 -0
  20. package/esm/log-channel.d.mts.map +1 -0
  21. package/esm/log-channel.mjs +75 -0
  22. package/esm/log-channel.mjs.map +1 -0
  23. package/esm/logger.d.mts +184 -0
  24. package/esm/logger.d.mts.map +1 -0
  25. package/esm/logger.mjs +282 -0
  26. package/esm/logger.mjs.map +1 -0
  27. package/esm/redact/redact.d.mts +25 -0
  28. package/esm/redact/redact.d.mts.map +1 -0
  29. package/esm/redact/redact.mjs +109 -0
  30. package/esm/redact/redact.mjs.map +1 -0
  31. package/esm/types.d.mts +129 -0
  32. package/esm/types.d.mts.map +1 -0
  33. package/esm/utils/capture-unhandled-errors.d.mts +16 -0
  34. package/esm/utils/capture-unhandled-errors.d.mts.map +1 -0
  35. package/esm/utils/capture-unhandled-errors.mjs +26 -0
  36. package/esm/utils/capture-unhandled-errors.mjs.map +1 -0
  37. package/esm/utils/clear-message.d.mts +8 -0
  38. package/esm/utils/clear-message.d.mts.map +1 -0
  39. package/esm/utils/clear-message.mjs +12 -0
  40. package/esm/utils/clear-message.mjs.map +1 -0
  41. package/esm/utils/index.mjs +5 -0
  42. package/esm/utils/safe-json-stringify.d.mts +14 -0
  43. package/esm/utils/safe-json-stringify.d.mts.map +1 -0
  44. package/esm/utils/safe-json-stringify.mjs +35 -0
  45. package/esm/utils/safe-json-stringify.mjs.map +1 -0
  46. package/llms-full.txt +1296 -0
  47. package/llms.txt +19 -0
  48. package/package.json +39 -39
  49. package/skills/capture-unhandled-errors/SKILL.md +103 -0
  50. package/skills/configure-logger/SKILL.md +105 -0
  51. package/skills/filter-log-entries/SKILL.md +120 -0
  52. package/skills/flush-logs-on-shutdown/SKILL.md +91 -0
  53. package/skills/logger-basics/SKILL.md +85 -0
  54. package/skills/overview/SKILL.md +86 -0
  55. package/skills/pick-log-channel/SKILL.md +139 -0
  56. package/skills/redact-sensitive-log-fields/SKILL.md +122 -0
  57. package/skills/test-logging-code/SKILL.md +169 -0
  58. package/skills/use-log-helpers/SKILL.md +66 -0
  59. package/skills/write-custom-log-channel/SKILL.md +160 -0
  60. package/cjs/channels/console-log.d.ts +0 -17
  61. package/cjs/channels/console-log.d.ts.map +0 -1
  62. package/cjs/channels/console-log.js +0 -47
  63. package/cjs/channels/console-log.js.map +0 -1
  64. package/cjs/channels/file-log.d.ts +0 -171
  65. package/cjs/channels/file-log.d.ts.map +0 -1
  66. package/cjs/channels/file-log.js +0 -293
  67. package/cjs/channels/file-log.js.map +0 -1
  68. package/cjs/channels/index.d.ts +0 -4
  69. package/cjs/channels/index.d.ts.map +0 -1
  70. package/cjs/channels/json-file-log.d.ts +0 -33
  71. package/cjs/channels/json-file-log.d.ts.map +0 -1
  72. package/cjs/channels/json-file-log.js +0 -164
  73. package/cjs/channels/json-file-log.js.map +0 -1
  74. package/cjs/index.d.ts +0 -6
  75. package/cjs/index.d.ts.map +0 -1
  76. package/cjs/index.js +0 -1
  77. package/cjs/index.js.map +0 -1
  78. package/cjs/log-channel.d.ts +0 -67
  79. package/cjs/log-channel.d.ts.map +0 -1
  80. package/cjs/log-channel.js +0 -88
  81. package/cjs/log-channel.js.map +0 -1
  82. package/cjs/logger.d.ts +0 -62
  83. package/cjs/logger.d.ts.map +0 -1
  84. package/cjs/logger.js +0 -124
  85. package/cjs/logger.js.map +0 -1
  86. package/cjs/types.d.ts +0 -104
  87. package/cjs/types.d.ts.map +0 -1
  88. package/cjs/utils/capture-unhandled-errors.d.ts +0 -2
  89. package/cjs/utils/capture-unhandled-errors.d.ts.map +0 -1
  90. package/cjs/utils/capture-unhandled-errors.js +0 -12
  91. package/cjs/utils/capture-unhandled-errors.js.map +0 -1
  92. package/cjs/utils/clear-message.d.ts +0 -5
  93. package/cjs/utils/clear-message.d.ts.map +0 -1
  94. package/cjs/utils/clear-message.js +0 -9
  95. package/cjs/utils/clear-message.js.map +0 -1
  96. package/cjs/utils/index.d.ts +0 -3
  97. package/cjs/utils/index.d.ts.map +0 -1
  98. package/esm/channels/console-log.d.ts +0 -17
  99. package/esm/channels/console-log.d.ts.map +0 -1
  100. package/esm/channels/console-log.js +0 -47
  101. package/esm/channels/console-log.js.map +0 -1
  102. package/esm/channels/file-log.d.ts +0 -171
  103. package/esm/channels/file-log.d.ts.map +0 -1
  104. package/esm/channels/file-log.js +0 -293
  105. package/esm/channels/file-log.js.map +0 -1
  106. package/esm/channels/index.d.ts +0 -4
  107. package/esm/channels/index.d.ts.map +0 -1
  108. package/esm/channels/json-file-log.d.ts +0 -33
  109. package/esm/channels/json-file-log.d.ts.map +0 -1
  110. package/esm/channels/json-file-log.js +0 -164
  111. package/esm/channels/json-file-log.js.map +0 -1
  112. package/esm/index.d.ts +0 -6
  113. package/esm/index.d.ts.map +0 -1
  114. package/esm/index.js +0 -1
  115. package/esm/index.js.map +0 -1
  116. package/esm/log-channel.d.ts +0 -67
  117. package/esm/log-channel.d.ts.map +0 -1
  118. package/esm/log-channel.js +0 -88
  119. package/esm/log-channel.js.map +0 -1
  120. package/esm/logger.d.ts +0 -62
  121. package/esm/logger.d.ts.map +0 -1
  122. package/esm/logger.js +0 -124
  123. package/esm/logger.js.map +0 -1
  124. package/esm/types.d.ts +0 -104
  125. package/esm/types.d.ts.map +0 -1
  126. package/esm/utils/capture-unhandled-errors.d.ts +0 -2
  127. package/esm/utils/capture-unhandled-errors.d.ts.map +0 -1
  128. package/esm/utils/capture-unhandled-errors.js +0 -12
  129. package/esm/utils/capture-unhandled-errors.js.map +0 -1
  130. package/esm/utils/clear-message.d.ts +0 -5
  131. package/esm/utils/clear-message.d.ts.map +0 -1
  132. package/esm/utils/clear-message.js +0 -9
  133. package/esm/utils/clear-message.js.map +0 -1
  134. package/esm/utils/index.d.ts +0 -3
  135. package/esm/utils/index.d.ts.map +0 -1
package/llms.txt ADDED
@@ -0,0 +1,19 @@
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): captureAnyUnhandledRejection() installs process.on('unhandledRejection') + ('uncaughtException') listeners routing failures through log.error('app', ...). Triggers: `captureAnyUnhandledRejection`, `unhandledRejection`, `uncaughtException`, `log.error`; "log unhandled promise rejections", "catch uncaught exceptions to a file", "record crashes before exit", "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')`.
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): Drop log entries — per-channel levels whitelist, per-channel filter predicate, logger-wide setMinLevel(level) fast path. Triggers: `levels`, `filter`, `minLevel`, `log.setMinLevel`, `shouldBeLogged`, `LoggingData`, `LogLevel`; "silence a noisy module", "route errors to a dedicated file", "raise global severity floor", "drop debug logs in prod"; typical import `import { log } from "@warlock.js/logger"`. Skip: custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; channel picks — `@warlock.js/logger/pick-log-channel/SKILL.md`; competing libs `pino.levels`, `winston.format.filter`, `debug` env var.
12
+ - [flush-logs-on-shutdown](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md): 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')`.
13
+ - [logger-basics](@warlock.js/logger/logger-basics/SKILL.md): Start with @warlock.js/logger — the log singleton, five levels (debug / info / warn / error / success), channel fan-out, foundations. Triggers: `log`, `Logger`, `log.info`, `log.error`, `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`.
14
+ - [overview](@warlock.js/logger/overview/SKILL.md): Front-door orientation for `@warlock.js/logger` — structured channel-based logging with five severity levels, PII redaction floor, buffered file/JSON channels, 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`, `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.
15
+ - [pick-log-channel](@warlock.js/logger/pick-log-channel/SKILL.md): Pick one of the three built-in channels — ConsoleLog (terminal), FileLog (plain text on disk), JSONFileLog (structured JSON for aggregators like Loki / Datadog / Elastic). Triggers: `ConsoleLog`, `FileLog`, `JSONFileLog`, `chunk`, `rotate`, `groupBy`, `maxFileSize`, `showContext`, `log.channel`; "log to a file", "rotate log files", "daily log chunks", "json logs for datadog / loki / elastic"; typical import `import { ConsoleLog, FileLog, JSONFileLog } from "@warlock.js/logger"`. Skip: custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; registration — `@warlock.js/logger/configure-logger/SKILL.md`; competing libs `winston-daily-rotate-file`, `pino-pretty`.
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
+ - [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`.
18
+ - [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`.
19
+ - [write-custom-log-channel](@warlock.js/logger/write-custom-log-channel/SKILL.md): Extend the abstract LogChannel class for custom sinks — Slack, database, HTTP endpoint, in-memory buffer. Triggers: `LogChannel`, `LogContract`, `LoggingData`, `shouldBeLogged`, `init`, `flushSync`, `terminal`; "log to slack", "log to a database", "send logs to datadog / loki HTTP api", "in-memory test capture channel", "build a custom log sink"; typical import `import { LogChannel, type LoggingData, type LogContract } from "@warlock.js/logger"`. Skip: built-in channels — `@warlock.js/logger/pick-log-channel/SKILL.md`; filtering — `@warlock.js/logger/filter-log-entries/SKILL.md`; competing libs `winston-transport`, `pino-transport`.
package/package.json CHANGED
@@ -1,40 +1,40 @@
1
1
  {
2
- "name": "@warlock.js/logger",
3
- "version": "4.0.174",
4
- "description": "A powerful logging system for messages and errors in nodejs.",
5
- "main": "./cjs/index.js",
6
- "scripts": {
7
- "test:coverage": "jest ./tests --coverage",
8
- "test:watch": "jest ./tests --watch",
9
- "test": "jest ./tests",
10
- "test:file": "jest"
11
- },
12
- "dependencies": {
13
- "@mongez/fs": "^3.0.4",
14
- "@mongez/copper": "^1.0.1",
15
- "@mongez/reinforcements": "^2.3.17",
16
- "dayjs": "^1.11.9"
17
- },
18
- "devDependencies": {
19
- "@types/jest": "^29.5.0",
20
- "jest": "^29.5.0",
21
- "ts-jest": "^29.0.5",
22
- "typescript": "^5.0.2"
23
- },
24
- "keywords": [
25
- "password",
26
- "hash",
27
- "make",
28
- "generate",
29
- "verify",
30
- "brute-force"
31
- ],
32
- "repository": {
33
- "type": "git",
34
- "url": "https://github.com/hassanzohdy/mongez-password"
35
- },
36
- "author": "hassanzohdy",
37
- "license": "MIT",
38
- "module": "./esm/index.js",
39
- "typings": "./cjs/index.d.ts"
40
- }
2
+ "name": "@warlock.js/logger",
3
+ "description": "A powerful logging system for messages and errors in nodejs.",
4
+ "keywords": [
5
+ "logger",
6
+ "log",
7
+ "logging",
8
+ "file-log",
9
+ "console-log"
10
+ ],
11
+ "author": "hassanzohdy",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/warlockjs/logger"
16
+ },
17
+ "dependencies": {
18
+ "@warlock.js/fs": "*",
19
+ "@mongez/copper": "^2.1.2",
20
+ "@mongez/reinforcements": "^3.2.0",
21
+ "dayjs": "^1.11.9",
22
+ "safe-stable-stringify": "^2.5.0"
23
+ },
24
+ "version": "4.1.1",
25
+ "main": "./cjs/index.cjs",
26
+ "module": "./esm/index.mjs",
27
+ "types": "./esm/index.d.mts",
28
+ "exports": {
29
+ ".": {
30
+ "import": {
31
+ "types": "./esm/index.d.mts",
32
+ "default": "./esm/index.mjs"
33
+ },
34
+ "require": {
35
+ "types": "./esm/index.d.mts",
36
+ "default": "./cjs/index.cjs"
37
+ }
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: capture-unhandled-errors
3
+ description: 'captureAnyUnhandledRejection() installs process.on(''unhandledRejection'') + (''uncaughtException'') listeners routing failures through log.error(''app'', ...). Triggers: `captureAnyUnhandledRejection`, `unhandledRejection`, `uncaughtException`, `log.error`; "log unhandled promise rejections", "catch uncaught exceptions to a file", "record crashes before exit", "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
+ ---
5
+
6
+ # Error capture — routing Node's unhandled errors through the logger
7
+
8
+ `captureAnyUnhandledRejection()` installs two process-level listeners so crashes are logged (not silently swallowed) before Node exits.
9
+
10
+ ## What it does
11
+
12
+ ```ts
13
+ import { captureAnyUnhandledRejection } from "@warlock.js/logger";
14
+
15
+ captureAnyUnhandledRejection();
16
+ ```
17
+
18
+ Registers:
19
+ - `process.on("unhandledRejection", reason => log.error("app", "unhandledRejection", reason))`
20
+ - `process.on("uncaughtException", error => log.error("app", "uncaughtException", error))`
21
+
22
+ Nothing else — the failure goes through `log.error` only, so it lands in your configured channels rather than bypassing them with a raw `console.log`.
23
+
24
+ ## When to call it
25
+
26
+ **Once**, at startup, **after** channels are registered. Typical place: immediately after your `log.configure({...})` call.
27
+
28
+ ```ts title="src/index.ts"
29
+ import {
30
+ log,
31
+ ConsoleLog,
32
+ FileLog,
33
+ captureAnyUnhandledRejection,
34
+ } from "@warlock.js/logger";
35
+
36
+ log.configure({
37
+ channels: [new ConsoleLog(), new FileLog({ levels: ["error"] })],
38
+ autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"], // ← important; see below
39
+ });
40
+
41
+ captureAnyUnhandledRejection();
42
+ ```
43
+
44
+ ## Pair with `autoFlushOn: ["beforeExit"]`
45
+
46
+ Without a flush on exit, here's what happens on a crash:
47
+
48
+ 1. Promise rejection fires → `log.error(...)` queues the error into `FileLog`'s buffer.
49
+ 2. Node exits.
50
+ 3. Buffer is never flushed. **The error that killed your app is lost.**
51
+
52
+ Including `"beforeExit"` in `autoFlushOn` closes the gap. Node fires `beforeExit` after the rejection handler resolves, the logger flushes, then Node exits. See [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md).
53
+
54
+ ## Idempotency — don't call it twice
55
+
56
+ Calling `captureAnyUnhandledRejection()` a second time registers a second pair of listeners. Your next rejection gets logged twice. There's no dedup; just call it once.
57
+
58
+ ## What it does **not** do
59
+
60
+ - **Does not swallow errors.** Node still exits after `uncaughtException` (this is the safe behavior — state is undefined). The logger just ensures the error is recorded first.
61
+ - **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
+ - **Does not hook `SIGTERM` / `SIGINT`** — use `enableAutoFlush` for signal flushes.
63
+ - **Does not filter.** Every rejection/exception is logged at `error` level with `module: "app"`. Filter per-channel if some noise slips in.
64
+
65
+ ## Checking an error was captured in tests
66
+
67
+ Don't mock `process.on` — use a capturing channel and emit the listener directly:
68
+
69
+ ```ts
70
+ import { log, captureAnyUnhandledRejection, LogChannel } from "@warlock.js/logger";
71
+ import type { LoggingData } from "@warlock.js/logger";
72
+
73
+ class Capture extends LogChannel {
74
+ public name = "capture";
75
+ public received: LoggingData[] = [];
76
+ public log(data: LoggingData) { this.received.push({ ...data }); }
77
+ }
78
+
79
+ it("routes unhandled rejections to the logger", async () => {
80
+ const capture = new Capture();
81
+ const originalChannels = log.channels;
82
+ log.channels = [capture];
83
+
84
+ captureAnyUnhandledRejection();
85
+ process.emit("unhandledRejection", new Error("boom"), Promise.resolve());
86
+
87
+ await new Promise((r) => setTimeout(r, 0));
88
+
89
+ expect(capture.received[0]!.module).toBe("app");
90
+ expect(capture.received[0]!.action).toBe("unhandledRejection");
91
+
92
+ log.channels = originalChannels;
93
+ });
94
+ ```
95
+
96
+ ## Module + action the capture uses
97
+
98
+ Both listeners log with:
99
+ - `module: "app"`
100
+ - `action: "unhandledRejection"` or `action: "uncaughtException"`
101
+ - `message`: the rejection reason / exception (keep it as the raw `Error` object — file channels capture the stack).
102
+
103
+ If you want these routed to a specific file, filter on `data.module === "app"`. See [`@warlock.js/logger/filter-log-entries/SKILL.md`](@warlock.js/logger/filter-log-entries/SKILL.md).
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: configure-logger
3
+ description: '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`.'
4
+ ---
5
+
6
+ # Setup — registering channels at startup
7
+
8
+ The logger is a singleton. Do all setup in one place, as early in the app entry point as possible.
9
+
10
+ ## The three channel-registration methods
11
+
12
+ | Method | Semantics |
13
+ |---|---|
14
+ | `log.addChannel(channel)` | **Appends.** Safe to call multiple times. |
15
+ | `log.setChannels([...])` | **Replaces** the full list. |
16
+ | `log.configure({ channels, autoFlushOn, redact, minLevel })` | **Replaces** channels if provided; installs auto-flush if provided; sets redact / minLevel if provided. All four are optional. |
17
+
18
+ All three return `this` — chainable.
19
+
20
+ ## Recommended pattern — one dedicated file
21
+
22
+ ```ts title="src/logger.ts"
23
+ import { log, ConsoleLog, FileLog, JSONFileLog } from "@warlock.js/logger";
24
+
25
+ if (process.env.NODE_ENV === "production") {
26
+ log.configure({
27
+ channels: [
28
+ new FileLog({ storagePath: "./storage/logs", chunk: "daily", rotate: true }),
29
+ new JSONFileLog({ storagePath: "./storage/logs-json", chunk: "daily" }),
30
+ ],
31
+ autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
32
+ });
33
+ } else if (process.env.NODE_ENV === "test") {
34
+ log.setChannels([]); // silence logger during tests
35
+ } else {
36
+ log.setChannels([new ConsoleLog()]);
37
+ }
38
+ ```
39
+
40
+ Import it once at the top of `src/index.ts`:
41
+
42
+ ```ts title="src/index.ts"
43
+ import "./logger"; // side-effect: configures singleton
44
+ import { log } from "@warlock.js/logger";
45
+
46
+ log.info("app", "start", "Server listening on :3000");
47
+ ```
48
+
49
+ ## What `configure({ autoFlushOn })` does
50
+
51
+ Registers one process-level handler per event that calls `log.flushSync()` before Node exits. See [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md) for the full behavior table.
52
+
53
+ ```ts
54
+ log.configure({
55
+ channels: [new FileLog()],
56
+ autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
57
+ });
58
+ // Now a buffered FileLog flushes on Ctrl+C, container stop, and natural exit.
59
+ ```
60
+
61
+ Calling `configure({ autoFlushOn })` a second time **replaces** previous handlers (not stacks them). Call `log.disableAutoFlush()` to tear them down.
62
+
63
+ ## Creating an isolated Logger
64
+
65
+ Rarely needed. Useful when a library wants its own channel list that doesn't share with the host app:
66
+
67
+ ```ts
68
+ import { Logger, ConsoleLog } from "@warlock.js/logger";
69
+
70
+ export const libraryLogger = new Logger();
71
+ libraryLogger.addChannel(new ConsoleLog({ filter: (d) => d.module === "my-lib" }));
72
+ ```
73
+
74
+ Every `new Logger()` gets a unique `id` (string, prefixed `"logger-"`).
75
+
76
+ ## Order matters — ANSI stripping across channels
77
+
78
+ `Logger.log` shallow-clones the entry per non-terminal channel before stripping ANSI codes. Registering a terminal channel (ConsoleLog) **after** a non-terminal one (FileLog) still works — ConsoleLog sees the original colored message. But if you register them in reverse and add a channel that mutates `data` in place, the non-terminal channel will see the terminal channel's version. Prefer the built-ins; custom channels should not mutate `data`.
79
+
80
+ ## When to call what
81
+
82
+ - **`addChannel`** — most common. Add channels as you discover you need them during setup.
83
+ - **`setChannels`** — when env branching makes the full list clear at once (production vs dev).
84
+ - **`configure`** — when you also want to install auto-flush, redact, or minLevel in the same call.
85
+
86
+ ## Combining everything
87
+
88
+ ```ts
89
+ log.configure({
90
+ channels: [
91
+ new ConsoleLog({ showContext: true }),
92
+ new FileLog({ chunk: "daily" }),
93
+ ],
94
+ autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
95
+ redact: { paths: ["context.password", "context.headers.authorization"] },
96
+ minLevel: process.env.LOG_LEVEL === "debug" ? "debug" : "info",
97
+ });
98
+ ```
99
+
100
+ See [`@warlock.js/logger/redact-sensitive-log-fields/SKILL.md`](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md) for the redact contract and [`@warlock.js/logger/filter-log-entries/SKILL.md`](@warlock.js/logger/filter-log-entries/SKILL.md) for `minLevel`.
101
+
102
+ ## See also
103
+
104
+ - [`@warlock.js/logger/pick-log-channel/SKILL.md`](@warlock.js/logger/pick-log-channel/SKILL.md) — what each built-in channel does
105
+ - [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md) — `autoFlushOn` event behavior
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: filter-log-entries
3
+ description: 'Drop log entries — per-channel levels whitelist, per-channel filter predicate, logger-wide setMinLevel(level) fast path. Triggers: `levels`, `filter`, `minLevel`, `log.setMinLevel`, `shouldBeLogged`, `LoggingData`, `LogLevel`; "silence a noisy module", "route errors to a dedicated file", "raise global severity floor", "drop debug logs in prod"; typical import `import { log } from "@warlock.js/logger"`. Skip: custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; channel picks — `@warlock.js/logger/pick-log-channel/SKILL.md`; competing libs `pino.levels`, `winston.format.filter`, `debug` env var.'
4
+ ---
5
+
6
+ # Filtering — `levels` + `filter` predicate + `minLevel`
7
+
8
+ Every channel can silently drop entries it doesn't care about. Three mechanisms stack: a logger-wide `minLevel` floor (cheapest), then per-channel `levels` whitelist, then per-channel `filter` predicate.
9
+
10
+ ## 1. `levels` — the per-channel whitelist
11
+
12
+ ```ts
13
+ new FileLog({ levels: ["error", "warn"] });
14
+ // debug/info/success entries → skipped
15
+ // error/warn entries → written
16
+ ```
17
+
18
+ - Omitting `levels` (or passing `[]`) means **allow all five**.
19
+ - No regex / no range — it's a literal whitelist of `LogLevel` strings.
20
+
21
+ ## 2. `filter` — the per-channel custom predicate
22
+
23
+ ```ts
24
+ new ConsoleLog({
25
+ filter: (data) => data.module !== "healthcheck",
26
+ });
27
+ // Every entry is passed to the predicate; return false → skip.
28
+ ```
29
+
30
+ - `data` is the full `LoggingData`: `{ type, module, action, message, context? }`.
31
+ - Predicate runs **after** `levels` — an entry blocked by `levels` never reaches `filter`.
32
+
33
+ ## 3. `minLevel` — the logger-wide severity floor
34
+
35
+ For the common "drop everything below X" case, skip the per-channel `levels` array and use the logger-wide fast path:
36
+
37
+ ```ts
38
+ log.setMinLevel("info");
39
+ // debug entries are dropped before fan-out — no channel ever sees them.
40
+
41
+ log.configure({ minLevel: "warn" }); // shorthand inside configure()
42
+ ```
43
+
44
+ Severity ordering: `debug < info ≈ success < warn < error`. `success` is treated as informational severity — `setMinLevel("warn")` drops it.
45
+
46
+ Pass `undefined` to clear:
47
+
48
+ ```ts
49
+ log.setMinLevel(undefined); // accept everything again
50
+ ```
51
+
52
+ This runs **before** the channel loop — cheaper than per-channel `levels` filters when you want a uniform floor. Per-channel `levels` and `filter` still run on top for channels that need a tighter or differently-shaped rule.
53
+
54
+ ## Combining — real patterns
55
+
56
+ ### Route errors to a dedicated file
57
+
58
+ ```ts
59
+ log.setChannels([
60
+ new ConsoleLog(),
61
+ new FileLog({
62
+ name: "errors",
63
+ levels: ["error", "warn"],
64
+ chunk: "daily",
65
+ }),
66
+ ]);
67
+ // ConsoleLog sees everything; errors.log only grows with warnings and errors.
68
+ ```
69
+
70
+ ### Silence a noisy module
71
+
72
+ ```ts
73
+ new ConsoleLog({
74
+ filter: (data) => data.module !== "socket.io",
75
+ });
76
+ ```
77
+
78
+ ### Keep the dev terminal focused
79
+
80
+ ```ts
81
+ // Only surface the subsystem you're actively working on
82
+ new ConsoleLog({
83
+ filter: (data) => data.module === "auth",
84
+ });
85
+ ```
86
+
87
+ ### Errors always pass, info only for one module
88
+
89
+ ```ts
90
+ new ConsoleLog({
91
+ filter: (data) => data.type === "error" || data.module === "payments",
92
+ });
93
+ ```
94
+
95
+ ## Where filtering happens
96
+
97
+ `LogChannel.shouldBeLogged(data)` runs both checks in order:
98
+
99
+ ```ts
100
+ // levels check — fast path
101
+ if (this.config("levels")?.length && !this.config("levels").includes(data.type)) return false;
102
+
103
+ // filter predicate — only runs if levels allowed it
104
+ const filter = this.config("filter");
105
+ if (filter) return filter(data);
106
+
107
+ return true;
108
+ ```
109
+
110
+ If you extend `LogChannel` to write a custom channel, call `this.shouldBeLogged(data)` first thing inside your `log(data)` method — you inherit both mechanisms for free. See [`@warlock.js/logger/write-custom-log-channel/SKILL.md`](@warlock.js/logger/write-custom-log-channel/SKILL.md).
111
+
112
+ ## Logger-wide custom filtering — not a thing
113
+
114
+ There is no `logger.setGlobalFilter()`. Each channel filters itself. If you want the same predicate everywhere, pass it to every channel constructor (or wrap your channels in a helper).
115
+
116
+ ## Performance note
117
+
118
+ Filters run on **every** entry per channel. A synchronous, cheap predicate is fine. Avoid `await` inside — the channel receives a fully-formed `LoggingData` and the filter is sync-only (type: `(data: LoggingData) => boolean`).
119
+
120
+ The `minLevel` check is the fastest of the three (single comparison before fan-out), so prefer it when "drop everything below X uniformly" matches your need.
@@ -0,0 +1,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.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
+ ## What `flushSync()` actually does
60
+
61
+ ```ts
62
+ log.flushSync();
63
+ // For every registered channel:
64
+ // if (channel.flushSync) channel.flushSync();
65
+ ```
66
+
67
+ - Synchronous I/O — blocks the event loop.
68
+ - Channels without `flushSync` (e.g. `ConsoleLog` — nothing to flush) are skipped silently.
69
+ - Works with and without `groupBy` on `FileLog` / `JSONFileLog`.
70
+ - No-op if every channel's buffer is empty.
71
+
72
+ `ConsoleLog` has no `flushSync` — it writes synchronously on every entry. `FileLog` and `JSONFileLog` both implement it.
73
+
74
+ ## Unhandled errors
75
+
76
+ 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.
77
+
78
+ ```ts
79
+ log.configure({
80
+ channels: [new FileLog({ levels: ["error"] })],
81
+ autoFlushOn: ["SIGINT", "SIGTERM", "beforeExit"],
82
+ });
83
+
84
+ captureAnyUnhandledRejection();
85
+ ```
86
+
87
+ ## What NOT to do
88
+
89
+ - **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); }`.
90
+ - **Don't call `process.exit()` inside `autoFlushOn` handlers** — signal handlers here already re-raise the signal. Forcing an exit breaks exit codes.
91
+ - **Don't rely on the 5-second flush interval for shutdown safety.** It's a throughput optimization, not a durability guarantee.
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: logger-basics
3
+ description: 'Start with @warlock.js/logger — the log singleton, five levels (debug / info / warn / error / success), channel fan-out, foundations. Triggers: `log`, `Logger`, `log.info`, `log.error`, `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. Three built-in channels (`ConsoleLog`, `FileLog`, `JSONFileLog`), an abstract `LogChannel` base for custom sinks, five severity levels, and a safe shutdown path via `Logger.enableAutoFlush(events)`.
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. **Five levels, closed union:** `"debug" | "info" | "warn" | "error" | "success"`. There are no custom levels today.
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 five 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); // failure path
58
+ log.success("module", "action", "operation done"); // explicit success
59
+ ```
60
+
61
+ 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).
62
+
63
+ ## Pick a skill
64
+
65
+ | If the task is about… | Load |
66
+ | --- | --- |
67
+ | 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) |
68
+ | Startup — registering channels, environment-based setup, the `configure` method | [`@warlock.js/logger/configure-logger/SKILL.md`](@warlock.js/logger/configure-logger/SKILL.md) |
69
+ | 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) |
70
+ | Graceful shutdown — `flushSync`, `autoFlushOn`, signal behavior | [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md) |
71
+ | 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) |
72
+ | Routing Node's `unhandledRejection` / `uncaughtException` through the logger | [`@warlock.js/logger/capture-unhandled-errors/SKILL.md`](@warlock.js/logger/capture-unhandled-errors/SKILL.md) |
73
+ | `log.assert(...)` and `log.timer(...)` shorthand helpers | [`@warlock.js/logger/use-log-helpers/SKILL.md`](@warlock.js/logger/use-log-helpers/SKILL.md) |
74
+ | 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) |
75
+ | 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) |
76
+
77
+ ## Things NOT to do
78
+
79
+ - 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.
80
+ - Don't set `extension` on `JSONFileLog` — it's hardcoded to `"json"` and your value is silently ignored.
81
+ - 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.
82
+ - Don't mix `autoFlushOn: ["SIGINT"]` with your own `process.on("SIGINT", ...)` handler — both fire, and ours re-raises mid-way through your async work.
83
+ - 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.
84
+ - Don't call `captureAnyUnhandledRejection()` more than once — it re-registers listeners every call and your rejections get logged N times.
85
+ - 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.