@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.
- package/README.md +145 -422
- package/cjs/index.cjs +1003 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/channels/console-log.d.mts +40 -0
- package/esm/channels/console-log.d.mts.map +1 -0
- package/esm/channels/console-log.mjs +51 -0
- package/esm/channels/console-log.mjs.map +1 -0
- package/esm/channels/file-log.d.mts +194 -0
- package/esm/channels/file-log.d.mts.map +1 -0
- package/esm/channels/file-log.mjs +267 -0
- package/esm/channels/file-log.mjs.map +1 -0
- package/esm/channels/index.mjs +5 -0
- package/esm/channels/json-file-log.d.mts +33 -0
- package/esm/channels/json-file-log.d.mts.map +1 -0
- package/esm/channels/json-file-log.mjs +137 -0
- package/esm/channels/json-file-log.mjs.map +1 -0
- package/esm/index.d.mts +11 -0
- package/esm/index.mjs +13 -0
- package/esm/log-channel.d.mts +78 -0
- package/esm/log-channel.d.mts.map +1 -0
- package/esm/log-channel.mjs +75 -0
- package/esm/log-channel.mjs.map +1 -0
- package/esm/logger.d.mts +184 -0
- package/esm/logger.d.mts.map +1 -0
- package/esm/logger.mjs +282 -0
- package/esm/logger.mjs.map +1 -0
- package/esm/redact/redact.d.mts +25 -0
- package/esm/redact/redact.d.mts.map +1 -0
- package/esm/redact/redact.mjs +109 -0
- package/esm/redact/redact.mjs.map +1 -0
- package/esm/types.d.mts +129 -0
- package/esm/types.d.mts.map +1 -0
- package/esm/utils/capture-unhandled-errors.d.mts +16 -0
- package/esm/utils/capture-unhandled-errors.d.mts.map +1 -0
- package/esm/utils/capture-unhandled-errors.mjs +26 -0
- package/esm/utils/capture-unhandled-errors.mjs.map +1 -0
- package/esm/utils/clear-message.d.mts +8 -0
- package/esm/utils/clear-message.d.mts.map +1 -0
- package/esm/utils/clear-message.mjs +12 -0
- package/esm/utils/clear-message.mjs.map +1 -0
- package/esm/utils/index.mjs +5 -0
- package/esm/utils/safe-json-stringify.d.mts +14 -0
- package/esm/utils/safe-json-stringify.d.mts.map +1 -0
- package/esm/utils/safe-json-stringify.mjs +35 -0
- package/esm/utils/safe-json-stringify.mjs.map +1 -0
- package/llms-full.txt +1296 -0
- package/llms.txt +19 -0
- package/package.json +39 -39
- package/skills/capture-unhandled-errors/SKILL.md +103 -0
- package/skills/configure-logger/SKILL.md +105 -0
- package/skills/filter-log-entries/SKILL.md +120 -0
- package/skills/flush-logs-on-shutdown/SKILL.md +91 -0
- package/skills/logger-basics/SKILL.md +85 -0
- package/skills/overview/SKILL.md +86 -0
- package/skills/pick-log-channel/SKILL.md +139 -0
- package/skills/redact-sensitive-log-fields/SKILL.md +122 -0
- package/skills/test-logging-code/SKILL.md +169 -0
- package/skills/use-log-helpers/SKILL.md +66 -0
- package/skills/write-custom-log-channel/SKILL.md +160 -0
- package/cjs/channels/console-log.d.ts +0 -17
- package/cjs/channels/console-log.d.ts.map +0 -1
- package/cjs/channels/console-log.js +0 -47
- package/cjs/channels/console-log.js.map +0 -1
- package/cjs/channels/file-log.d.ts +0 -171
- package/cjs/channels/file-log.d.ts.map +0 -1
- package/cjs/channels/file-log.js +0 -293
- package/cjs/channels/file-log.js.map +0 -1
- package/cjs/channels/index.d.ts +0 -4
- package/cjs/channels/index.d.ts.map +0 -1
- package/cjs/channels/json-file-log.d.ts +0 -33
- package/cjs/channels/json-file-log.d.ts.map +0 -1
- package/cjs/channels/json-file-log.js +0 -164
- package/cjs/channels/json-file-log.js.map +0 -1
- package/cjs/index.d.ts +0 -6
- package/cjs/index.d.ts.map +0 -1
- package/cjs/index.js +0 -1
- package/cjs/index.js.map +0 -1
- package/cjs/log-channel.d.ts +0 -67
- package/cjs/log-channel.d.ts.map +0 -1
- package/cjs/log-channel.js +0 -88
- package/cjs/log-channel.js.map +0 -1
- package/cjs/logger.d.ts +0 -62
- package/cjs/logger.d.ts.map +0 -1
- package/cjs/logger.js +0 -124
- package/cjs/logger.js.map +0 -1
- package/cjs/types.d.ts +0 -104
- package/cjs/types.d.ts.map +0 -1
- package/cjs/utils/capture-unhandled-errors.d.ts +0 -2
- package/cjs/utils/capture-unhandled-errors.d.ts.map +0 -1
- package/cjs/utils/capture-unhandled-errors.js +0 -12
- package/cjs/utils/capture-unhandled-errors.js.map +0 -1
- package/cjs/utils/clear-message.d.ts +0 -5
- package/cjs/utils/clear-message.d.ts.map +0 -1
- package/cjs/utils/clear-message.js +0 -9
- package/cjs/utils/clear-message.js.map +0 -1
- package/cjs/utils/index.d.ts +0 -3
- package/cjs/utils/index.d.ts.map +0 -1
- package/esm/channels/console-log.d.ts +0 -17
- package/esm/channels/console-log.d.ts.map +0 -1
- package/esm/channels/console-log.js +0 -47
- package/esm/channels/console-log.js.map +0 -1
- package/esm/channels/file-log.d.ts +0 -171
- package/esm/channels/file-log.d.ts.map +0 -1
- package/esm/channels/file-log.js +0 -293
- package/esm/channels/file-log.js.map +0 -1
- package/esm/channels/index.d.ts +0 -4
- package/esm/channels/index.d.ts.map +0 -1
- package/esm/channels/json-file-log.d.ts +0 -33
- package/esm/channels/json-file-log.d.ts.map +0 -1
- package/esm/channels/json-file-log.js +0 -164
- package/esm/channels/json-file-log.js.map +0 -1
- package/esm/index.d.ts +0 -6
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -1
- package/esm/index.js.map +0 -1
- package/esm/log-channel.d.ts +0 -67
- package/esm/log-channel.d.ts.map +0 -1
- package/esm/log-channel.js +0 -88
- package/esm/log-channel.js.map +0 -1
- package/esm/logger.d.ts +0 -62
- package/esm/logger.d.ts.map +0 -1
- package/esm/logger.js +0 -124
- package/esm/logger.js.map +0 -1
- package/esm/types.d.ts +0 -104
- package/esm/types.d.ts.map +0 -1
- package/esm/utils/capture-unhandled-errors.d.ts +0 -2
- package/esm/utils/capture-unhandled-errors.d.ts.map +0 -1
- package/esm/utils/capture-unhandled-errors.js +0 -12
- package/esm/utils/capture-unhandled-errors.js.map +0 -1
- package/esm/utils/clear-message.d.ts +0 -5
- package/esm/utils/clear-message.d.ts.map +0 -1
- package/esm/utils/clear-message.js +0 -9
- package/esm/utils/clear-message.js.map +0 -1
- package/esm/utils/index.d.ts +0 -3
- 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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|