@warlock.js/logger 5.34.1 → 5.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/logger` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.34.2 - 2026-10-19
8
+
9
+ ### Fixed
10
+
11
+ - Merge package orientation into the grouped skill index and remove the duplicate overview topic; preserve that orientation in generated LLM projections.
12
+
7
13
  ## 5.33.0 - 2026-10-19
8
14
 
9
15
  ### Added
package/llms-full.txt CHANGED
@@ -4,6 +4,73 @@
4
4
 
5
5
  > Generated artifact. Concatenates every SKILL.md and reference file under `@warlock.js/logger/skills/`. Re-run `node scripts/generate-llms.mjs` after any change.
6
6
 
7
+ ## Package orientation
8
+
9
+ ---
10
+ description: 'Structured logging for Warlock.js with pluggable channels, redaction and shutdown flushing. Exports `log`, `Logger`, `LogChannel`, `ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, `applyRedact`, `DEFAULT_REDACT_KEYS`, `captureAnyUnhandledRejection`. Use for: "log an error with context", "write logs to a file as JSON", "send errors to Sentry", "hide passwords and tokens in logs", "flush logs before exit", "catch unhandled rejections", "write my own log channel". Not this package: HTTP request handling and error responses live in @warlock.js/core; scheduled jobs are @warlock.js/scheduler.'
11
+ ---
12
+
13
+ # @warlock.js/logger
14
+
15
+ The `log` singleton (a `Logger`) sends each entry, with module, action, level and message, to every configured `LogChannel`. Channels decide where entries go (console, file, JSON file, Sentry, custom) and can filter by level or module.
16
+
17
+ Works standalone in Node projects; core is not required. Levels are `debug`, `info`, `warn`, `error`, `success`, and `fatal` (above `error`).
18
+
19
+ ## The 80% path
20
+
21
+ 1. Start with `logger-basics.md`.
22
+ 2. Pick channels: `pick-log-channel.md`; set them up: `configure-logger.md`.
23
+ 3. Log via `log.info/warn/error` or helpers: `use-log-helpers.md`.
24
+ 4. Redact secrets: `redact-sensitive-log-fields.md`; narrow output: `filter-log-entries.md`.
25
+ 5. Production safety: `capture-unhandled-errors.md`, `flush-logs-on-shutdown.md`, `ship-logs-to-sentry.md`.
26
+ 6. Extend or test: `write-custom-log-channel.md`, `test-logging-code.md`.
27
+
28
+ ## Conventions and pitfalls
29
+
30
+ - `fatal` does not flush or exit by itself; the caller awaits `log.flush()` and then exits.
31
+ - File channels buffer, so an unflushed process exit loses the tail of the log.
32
+ - Configure redaction before logging request bodies or auth data; the default key set is a floor, not a guarantee.
33
+ - Log structured context objects rather than interpolated strings, so channels and Sentry can index them.
34
+
35
+ ## Server-only package
36
+
37
+ `@warlock.js/logger`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/logger` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
38
+
39
+ ## When to reach for it
40
+
41
+ - Building a Node service that needs **structured** logs (key-value pairs, not bare strings) and you want them to land in multiple destinations (console for dev, JSON file for prod, third-party sink for audits) without rewriting the call sites.
42
+ - You'd reach for **pino** or **winston** but want a smaller surface that's already wired into Warlock conventions (`module / action / message` shape, redaction floor, signal flush built-in).
43
+ - Your team agrees that **`console.log` doesn't survive contact with production** — you need filtering, level routing, channel-specific sinks, and a redaction story before secrets leak into Slack/Datadog.
44
+
45
+ Skip if your code is a throwaway script where `console.log` is genuinely fine — there's no value in adding a dependency for one-off logs.
46
+
47
+ ## The mental model in one paragraph
48
+
49
+ You write `log.info("auth", "login", "user signed in", { userId })`. The logger fans that single entry out to every registered channel (`ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, or your custom subclass). Each channel decides whether to emit it (per-level whitelist, per-channel filter predicate, logger-wide minimum severity). Redaction runs once at the logger level and can be extended per channel — never relaxed. Buffered channels (file + JSON file) drain on flush — synchronously via `log.flushSync()` / `enableAutoFlush(['SIGINT', 'SIGTERM', 'beforeExit'])`, or asynchronously via `await log.flush()` (the only path that works for network channels like `SentryLog`). That's the whole package.
50
+
51
+ ## Built-in channels at a glance
52
+
53
+ | Channel | Sink | `terminal` | Buffered |
54
+ | ------------- | --------------------------- | ----------------------- | ----------------------------------------------------------------------- |
55
+ | `ConsoleLog` | `process.stdout` | `true` (colors kept) | no |
56
+ | `FileLog` | `.log` files | `false` (ANSI stripped) | yes (5s timer or 100-entry buffer) |
57
+ | `JSONFileLog` | `.json` files | `false` (ANSI stripped) | yes (same buffering) |
58
+ | `SentryLog` | Sentry events + breadcrumbs | `false` | via the Sentry SDK's own transport (`@sentry/node` is an optional peer) |
59
+
60
+ `terminal: true` is the flag that decides whether the channel sees raw colored messages or stripped plain text. Custom channels: pick `true` if you write to a terminal, `false` for anything else.
61
+
62
+ ## What this package deliberately doesn't do
63
+
64
+ - **Distributed tracing.** Use OpenTelemetry. Logger gets you structured local logs with `module / action`; trace correlation is a different problem.
65
+ - **Log aggregator integrations beyond Sentry.** `SentryLog` is bundled (with `@sentry/node` as an optional peer). For Datadog, Loki, ELK, etc., either write a custom channel that POSTs to the aggregator or use `JSONFileLog` + a sidecar (fluentbit, vector, promtail).
66
+ - **Pretty-printing of arbitrary objects.** `ConsoleLog` has a `showContext` flag that runs `util.inspect` on the context object; for richer formatting, use `JSONFileLog` and view the file through your favorite viewer.
67
+ - **Log analysis.** Querying / aggregating / alerting is on the sink side (Loki, ELK, Datadog).
68
+
69
+ ## See also
70
+
71
+ - The `warlock-conventions` topic of the `warlock-js-core` skill — the parent framework's conventions; logger is one of its foundation packages and ships transitively when you install core.
72
+
73
+
7
74
  ## capture-unhandled-errors `@warlock.js/logger/capture-unhandled-errors/SKILL.md`
8
75
 
9
76
  ---
@@ -577,115 +644,6 @@ Every call signature is the same — `module`, `action`, `message`, optional `co
577
644
  - 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.
578
645
 
579
646
 
580
- ## overview `@warlock.js/logger/overview/SKILL.md`
581
-
582
- ---
583
- name: overview
584
- description: "@warlock.js/logger overview for orientation and choosing the focused skill for the task."
585
- ---
586
-
587
- # `@warlock.js/logger` — overview
588
-
589
- Structured logging for Node. Six severity levels (with `fatal` strictly above `error`), a singleton plus a `Logger` class, channel-based fan-out (one entry → many sinks), PII redaction as a floor that channels can extend, buffered file writes with signal-triggered flush on shutdown, an awaitable async `log.flush()` for network/async channels, an optional Sentry channel, and a couple of ergonomic helpers (`timer`, `assert`) that turn boilerplate into one-liners.
590
-
591
- Ships standalone — `@warlock.js/core` is not required. Drop it into any Node project.
592
-
593
- ## Server-only package
594
-
595
- `@warlock.js/logger`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/logger` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
596
-
597
- ## When to reach for it
598
-
599
- - Building a Node service that needs **structured** logs (key-value pairs, not bare strings) and you want them to land in multiple destinations (console for dev, JSON file for prod, third-party sink for audits) without rewriting the call sites.
600
- - You'd reach for **pino** or **winston** but want a smaller surface that's already wired into Warlock conventions (`module / action / message` shape, redaction floor, signal flush built-in).
601
- - Your team agrees that **`console.log` doesn't survive contact with production** — you need filtering, level routing, channel-specific sinks, and a redaction story before secrets leak into Slack/Datadog.
602
-
603
- Skip if your code is a throwaway script where `console.log` is genuinely fine — there's no value in adding a dependency for one-off logs.
604
-
605
- ## The mental model in one paragraph
606
-
607
- You write `log.info("auth", "login", "user signed in", { userId })`. The logger fans that single entry out to every registered channel (`ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, or your custom subclass). Each channel decides whether to emit it (per-level whitelist, per-channel filter predicate, logger-wide minimum severity). Redaction runs once at the logger level and can be extended per channel — never relaxed. Buffered channels (file + JSON file) drain on flush — synchronously via `log.flushSync()` / `enableAutoFlush(['SIGINT', 'SIGTERM', 'beforeExit'])`, or asynchronously via `await log.flush()` (the only path that works for network channels like `SentryLog`). That's the whole package.
608
-
609
- ## Skills index
610
-
611
- Eleven task skills cover everything. Load the one that matches your job — most callers only ever need `logger-basics` + `configure-logger` + `pick-log-channel`.
612
-
613
- ### Foundations
614
-
615
- #### `logger-basics`
616
-
617
- Start here. The `log` singleton, the six levels (`debug` / `info` / `warn` / `error` / `success` / `fatal`), how fan-out works, the `module / action / message / context` shape every entry carries.
618
-
619
- #### `configure-logger`
620
-
621
- Wire channels at boot — `log.addChannel`, `log.setChannels`, `log.configure({ channels, autoFlushOn, redact, minLevel })`. Branch on `NODE_ENV`, replace the channel list, isolate a library's logger from the host singleton.
622
-
623
- ### Channels
624
-
625
- #### `pick-log-channel`
626
-
627
- Pick one of the four built-ins: `ConsoleLog` (terminal, colored), `FileLog` (plain `.log` on disk with rotation), `JSONFileLog` (structured JSON for aggregators — Datadog, Loki, ELK), `SentryLog` (errors + breadcrumbs to Sentry; `@sentry/node` is an optional peer).
628
-
629
- #### `ship-logs-to-sentry`
630
-
631
- The `SentryLog` channel in depth — event-vs-breadcrumb level mapping, dual init modes (reuse an existing client or pass `options`), draining via `Sentry.flush(timeout)`, behavior when the optional peer isn't installed.
632
-
633
- #### `write-custom-log-channel`
634
-
635
- Extend `LogChannel<Options>` for sinks the built-ins don't cover — Slack, HTTP endpoint, in-memory buffer, database. The lazy `init()` lifecycle (`setTimeout(0)`), the `terminal: true/false` ANSI-stripping behavior, and the difference between `flush()` (async, network) and `flushSync()` (sync, files) are subtle — read this skill before subclassing.
636
-
637
- ### Production concerns
638
-
639
- #### `redact-sensitive-log-fields`
640
-
641
- Strip secrets before they reach a sink. Logger-wide `setRedact({ paths, censor })` is the security floor; per-channel `redact` configs add paths (never remove). Dotted-glob paths (`*`, `**`); censor as string or function `(value, path) => any`.
642
-
643
- #### `filter-log-entries`
644
-
645
- Drop entries before they cost anything. Logger-wide `setMinLevel("info")` is the fast path; per-channel `levels` array + `filter` predicate for fine control.
646
-
647
- #### `flush-logs-on-shutdown`
648
-
649
- Buffered channels need explicit drain. `log.flushSync()` (sync) for file channels — also wired by `enableAutoFlush(['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK', 'SIGUSR2', 'beforeExit'])`. `await log.flush()` (async) for network/async channels like `SentryLog` — the only path that can await an HTTPS round-trip on a graceful shutdown.
650
-
651
- #### `capture-unhandled-errors`
652
-
653
- `captureAnyUnhandledRejection()` hooks `unhandledRejection` (→ `log.error("app", ...)`, process kept alive) and `uncaughtException` (→ `log.fatal("app", ...)` then `process.exit(1)` — restoring the non-zero exit the listener would otherwise suppress, so a fatal crash never becomes a silent `exit 0`; opt out with `{ exitOnUncaughtException: false }`). It flushes before exiting and falls back to `console.error` when no terminal channel is set. One call at startup.
654
-
655
- ### Ergonomics + testing
656
-
657
- #### `use-log-helpers`
658
-
659
- Two shortcuts every `Logger` exposes: `log.assert(condition, module, action, message, context?)` logs an error only when the condition is falsy (free on the happy path); `log.timer(module, action)` returns an end-function that emits `info` with a measured `durationMs`.
660
-
661
- #### `test-logging-code`
662
-
663
- Silence the logger globally in tests via `log.setChannels([])` in `setupFiles`. Assert specific entries with a capturing `LogChannel` subclass — it proves an entry was actually delivered through the pipeline (filters, redaction), not merely that a method was called, and it isolates cleanly by swapping `log.channels`.
664
-
665
- ## Built-in channels at a glance
666
-
667
- | Channel | Sink | `terminal` | Buffered |
668
- | ------------- | --------------------------- | ----------------------- | ----------------------------------------------------------------------- |
669
- | `ConsoleLog` | `process.stdout` | `true` (colors kept) | no |
670
- | `FileLog` | `.log` files | `false` (ANSI stripped) | yes (5s timer or 100-entry buffer) |
671
- | `JSONFileLog` | `.json` files | `false` (ANSI stripped) | yes (same buffering) |
672
- | `SentryLog` | Sentry events + breadcrumbs | `false` | via the Sentry SDK's own transport (`@sentry/node` is an optional peer) |
673
-
674
- `terminal: true` is the flag that decides whether the channel sees raw colored messages or stripped plain text. Custom channels: pick `true` if you write to a terminal, `false` for anything else.
675
-
676
- ## What this package deliberately doesn't do
677
-
678
- - **Distributed tracing.** Use OpenTelemetry. Logger gets you structured local logs with `module / action`; trace correlation is a different problem.
679
- - **Log aggregator integrations beyond Sentry.** `SentryLog` is bundled (with `@sentry/node` as an optional peer). For Datadog, Loki, ELK, etc., either write a custom channel that POSTs to the aggregator or use `JSONFileLog` + a sidecar (fluentbit, vector, promtail).
680
- - **Pretty-printing of arbitrary objects.** `ConsoleLog` has a `showContext` flag that runs `util.inspect` on the context object; for richer formatting, use `JSONFileLog` and view the file through your favorite viewer.
681
- - **Log analysis.** Querying / aggregating / alerting is on the sink side (Loki, ELK, Datadog).
682
-
683
- ## See also
684
-
685
- - The `warlock-conventions` topic of the `warlock-js-core` skill — the parent framework's conventions; logger is one of its foundation packages and ships transitively when you install core.
686
- - When synced via agent-kit, this `overview` topic is flattened to the front-door skill `.claude/skills/warlock-js-logger-overview/` — every cross-link above names the topic (and the owning skill, across packages) instead of a file path so it survives that flattening.
687
-
688
-
689
647
  ## pick-log-channel `@warlock.js/logger/pick-log-channel/SKILL.md`
690
648
 
691
649
  ---
package/llms.txt CHANGED
@@ -6,12 +6,12 @@
6
6
 
7
7
  ## Skills
8
8
 
9
+ - [Package orientation](@warlock.js/logger/skills/index.md): Structured logging for Warlock.js with pluggable channels, redaction and shutdown flushing. Exports `log`, `Logger`, `LogChannel`, `ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, `applyRedact`, `DEFAULT_REDACT_KEYS`, `captureAnyUnhandledRejection`. Use for: "log an error with context", "write logs to a file as JSON", "send errors to Sentry", "hide passwords and tokens in logs", "flush logs before exit", "catch unhandled rejections", "write my own log channel". Not this package: HTTP request handling and error responses live in @warlock.js/core; scheduled jobs are @warlock.js/scheduler.
9
10
  - [capture-unhandled-errors](@warlock.js/logger/capture-unhandled-errors/SKILL.md): captureAnyUnhandledRejection() installs process.on('unhandledRejection') → log.error and process.on('uncaughtException') → log.fatal + process.exit(1) so process-level failures land in your channels and a fatal crash is never silently swallowed into exit 0. Triggers: `captureAnyUnhandledRejection`, `exitOnUncaughtException`, `unhandledRejection`, `uncaughtException`, `log.error`, `log.fatal`; "log unhandled promise rejections", "catch uncaught exceptions to a file", "record crashes before exit", "server exits 0 with no error", "silent exit / production server stopped", "global error handler with logger"; typical import `import { captureAnyUnhandledRejection, log } from "@warlock.js/logger"`. Skip: flushing — the `flush-logs-on-shutdown` topic; filtering — the `filter-log-entries` topic; competing `Sentry.init`, `@sentry/node`; native `process.on('unhandledRejection')`.
10
11
  - [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 — the `pick-log-channel` topic; flushing — the `flush-logs-on-shutdown` topic; redaction — the `redact-sensitive-log-fields` topic; competing libs `winston.createLogger`, `pino`.
11
12
  - [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 — the `write-custom-log-channel` topic; channel picks — the `pick-log-channel` topic; competing libs `pino.levels`, `winston.format.filter`, `debug` env var.
12
13
  - [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.flush`, `log.flushSync`, `onChannelError`, `log.flush({ timeout })`, `autoFlushOn`, `enableAutoFlush`, `disableAutoFlush`, `SIGINT`, `SIGTERM`, `beforeExit`; "drain logs before exit", "await log.flush() before process.exit", "drain async or network channels on shutdown", "wire SIGTERM for container shutdown", "my logs never showed after a crash", "graceful shutdown logging"; typical import `import { log, FileLog } from "@warlock.js/logger"`. Skip: error capture — the `capture-unhandled-errors` topic; custom sinks — the `write-custom-log-channel` topic; competing `pino.final`, `winston.end`; native `process.on('exit')`.
13
14
  - [logger-basics](@warlock.js/logger/logger-basics/SKILL.md): Start here for @warlock.js/logger channels, structured entries, transports, and application logging setup.
14
- - [overview](@warlock.js/logger/overview/SKILL.md): @warlock.js/logger overview for orientation and choosing the focused skill for the task.
15
15
  - [pick-log-channel](@warlock.js/logger/pick-log-channel/SKILL.md): Pick one of the four built-in channels — ConsoleLog (terminal), FileLog (plain text on disk), JSONFileLog (structured JSON for aggregators like Loki / Datadog / Elastic), SentryLog (forwards errors + breadcrumbs to Sentry). Triggers: `ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, `chunk`, `rotate`, `groupBy`, `maxFileSize`, `showContext`, `log.channel`; "log to a file", "rotate log files", "daily log chunks", "json logs for datadog / loki / elastic", "send logs to Sentry"; typical import `import { ConsoleLog, FileLog, JSONFileLog, SentryLog } from "@warlock.js/logger"`. Skip: Sentry-specific setup — the `ship-logs-to-sentry` topic; custom sinks — the `write-custom-log-channel` topic; registration — the `configure-logger` topic; competing libs `winston-daily-rotate-file`, `pino-pretty`.
16
16
  - [redact-sensitive-log-fields](@warlock.js/logger/redact-sensitive-log-fields/SKILL.md): Strip secrets from log output — a built-in secret-key denylist on by default (DEFAULT_REDACT_KEYS), plus two-layer additive redaction via log.configure({redact: {paths, keys}}) (logger floor) + per-channel redact (more on top). Dotted glob paths (*, **). Triggers: `redact`, `paths`, `keys`, `defaultKeys`, `censor`, `log.setRedact`, `applyRedact`, `maskEmail`, `maskEmailsInText`, `registerLogSecret`, `maskRegisteredLogSecrets`; "redact passwords in logs", "strip tokens from log output", "hide authorization headers", "scrub PII before logging", "turn off default redaction"; typical import `import { log } from "@warlock.js/logger"`. Skip: filtering — the `filter-log-entries` topic; custom sinks — the `write-custom-log-channel` topic; competing libs `pino.redact`, `fast-redact`.
17
17
  - [ship-logs-to-sentry](@warlock.js/logger/ship-logs-to-sentry/SKILL.md): Forward log entries to Sentry with the SentryLog channel — error/warn become events (captureException/captureMessage), every other level a breadcrumb (no quota). @sentry/node is an OPTIONAL peer, lazily imported. Triggers: `SentryLog`, `@sentry/node`, `eventLevels`, `flushTimeout`, `Sentry.flush`, `captureException`, `addBreadcrumb`, `withScope`; "send logs to Sentry", "report errors to Sentry", "Sentry log channel", "Sentry breadcrumbs from logs", "log channel for Sentry"; typical import `import { SentryLog } from "@warlock.js/logger"`. Skip: file/console channels — the `pick-log-channel` topic; custom sinks — the `write-custom-log-channel` topic; graceful-shutdown flushing — the `flush-logs-on-shutdown` topic; Slack alerting recipe.
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "dependencies": {
5
5
  "@mongez/copper": "^2.1.3",
6
6
  "@mongez/reinforcements": "^4.0.2",
7
- "@warlock.js/fs": "5.34.1",
7
+ "@warlock.js/fs": "5.34.2",
8
8
  "dayjs": "^1.11.9",
9
9
  "safe-stable-stringify": "^2.5.0"
10
10
  },
@@ -33,7 +33,7 @@
33
33
  "warlock": {
34
34
  "environment": "server"
35
35
  },
36
- "version": "5.34.1",
36
+ "version": "5.34.2",
37
37
  "main": "./cjs/index.cjs",
38
38
  "module": "./esm/index.mjs",
39
39
  "types": "./esm/index.d.mts",
package/skills/index.md CHANGED
@@ -6,9 +6,11 @@ description: 'Structured logging for Warlock.js with pluggable channels, redacti
6
6
 
7
7
  The `log` singleton (a `Logger`) sends each entry, with module, action, level and message, to every configured `LogChannel`. Channels decide where entries go (console, file, JSON file, Sentry, custom) and can filter by level or module.
8
8
 
9
+ Works standalone in Node projects; core is not required. Levels are `debug`, `info`, `warn`, `error`, `success`, and `fatal` (above `error`).
10
+
9
11
  ## The 80% path
10
12
 
11
- 1. Orient: `overview.md`, `logger-basics.md`.
13
+ 1. Start with `logger-basics.md`.
12
14
  2. Pick channels: `pick-log-channel.md`; set them up: `configure-logger.md`.
13
15
  3. Log via `log.info/warn/error` or helpers: `use-log-helpers.md`.
14
16
  4. Redact secrets: `redact-sensitive-log-fields.md`; narrow output: `filter-log-entries.md`.
@@ -21,3 +23,41 @@ The `log` singleton (a `Logger`) sends each entry, with module, action, level an
21
23
  - File channels buffer, so an unflushed process exit loses the tail of the log.
22
24
  - Configure redaction before logging request bodies or auth data; the default key set is a floor, not a guarantee.
23
25
  - Log structured context objects rather than interpolated strings, so channels and Sentry can index them.
26
+
27
+ ## Server-only package
28
+
29
+ `@warlock.js/logger`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/logger` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
30
+
31
+ ## When to reach for it
32
+
33
+ - Building a Node service that needs **structured** logs (key-value pairs, not bare strings) and you want them to land in multiple destinations (console for dev, JSON file for prod, third-party sink for audits) without rewriting the call sites.
34
+ - You'd reach for **pino** or **winston** but want a smaller surface that's already wired into Warlock conventions (`module / action / message` shape, redaction floor, signal flush built-in).
35
+ - Your team agrees that **`console.log` doesn't survive contact with production** — you need filtering, level routing, channel-specific sinks, and a redaction story before secrets leak into Slack/Datadog.
36
+
37
+ Skip if your code is a throwaway script where `console.log` is genuinely fine — there's no value in adding a dependency for one-off logs.
38
+
39
+ ## The mental model in one paragraph
40
+
41
+ You write `log.info("auth", "login", "user signed in", { userId })`. The logger fans that single entry out to every registered channel (`ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, or your custom subclass). Each channel decides whether to emit it (per-level whitelist, per-channel filter predicate, logger-wide minimum severity). Redaction runs once at the logger level and can be extended per channel — never relaxed. Buffered channels (file + JSON file) drain on flush — synchronously via `log.flushSync()` / `enableAutoFlush(['SIGINT', 'SIGTERM', 'beforeExit'])`, or asynchronously via `await log.flush()` (the only path that works for network channels like `SentryLog`). That's the whole package.
42
+
43
+ ## Built-in channels at a glance
44
+
45
+ | Channel | Sink | `terminal` | Buffered |
46
+ | ------------- | --------------------------- | ----------------------- | ----------------------------------------------------------------------- |
47
+ | `ConsoleLog` | `process.stdout` | `true` (colors kept) | no |
48
+ | `FileLog` | `.log` files | `false` (ANSI stripped) | yes (5s timer or 100-entry buffer) |
49
+ | `JSONFileLog` | `.json` files | `false` (ANSI stripped) | yes (same buffering) |
50
+ | `SentryLog` | Sentry events + breadcrumbs | `false` | via the Sentry SDK's own transport (`@sentry/node` is an optional peer) |
51
+
52
+ `terminal: true` is the flag that decides whether the channel sees raw colored messages or stripped plain text. Custom channels: pick `true` if you write to a terminal, `false` for anything else.
53
+
54
+ ## What this package deliberately doesn't do
55
+
56
+ - **Distributed tracing.** Use OpenTelemetry. Logger gets you structured local logs with `module / action`; trace correlation is a different problem.
57
+ - **Log aggregator integrations beyond Sentry.** `SentryLog` is bundled (with `@sentry/node` as an optional peer). For Datadog, Loki, ELK, etc., either write a custom channel that POSTs to the aggregator or use `JSONFileLog` + a sidecar (fluentbit, vector, promtail).
58
+ - **Pretty-printing of arbitrary objects.** `ConsoleLog` has a `showContext` flag that runs `util.inspect` on the context object; for richer formatting, use `JSONFileLog` and view the file through your favorite viewer.
59
+ - **Log analysis.** Querying / aggregating / alerting is on the sink side (Loki, ELK, Datadog).
60
+
61
+ ## See also
62
+
63
+ - The `warlock-conventions` topic of the `warlock-js-core` skill — the parent framework's conventions; logger is one of its foundation packages and ships transitively when you install core.
@@ -1,105 +0,0 @@
1
- ---
2
- name: overview
3
- description: "@warlock.js/logger overview for orientation and choosing the focused skill for the task."
4
- ---
5
-
6
- # `@warlock.js/logger` — overview
7
-
8
- Structured logging for Node. Six severity levels (with `fatal` strictly above `error`), a singleton plus a `Logger` class, channel-based fan-out (one entry → many sinks), PII redaction as a floor that channels can extend, buffered file writes with signal-triggered flush on shutdown, an awaitable async `log.flush()` for network/async channels, an optional Sentry channel, and a couple of ergonomic helpers (`timer`, `assert`) that turn boilerplate into one-liners.
9
-
10
- Ships standalone — `@warlock.js/core` is not required. Drop it into any Node project.
11
-
12
- ## Server-only package
13
-
14
- `@warlock.js/logger`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. This is build-boundary metadata read by `@warlock.js/web`'s Gate A (import resolution) and Gate C (emitted-bundle verification), not application behavior. App code that reaches the client bundle (including page and layout modules) must not value-import `@warlock.js/logger` — Gate A refuses the build. A type-only import (`import type { ... }`) is allowed. Server loaders, controllers, and modules may import it freely.
15
-
16
- ## When to reach for it
17
-
18
- - Building a Node service that needs **structured** logs (key-value pairs, not bare strings) and you want them to land in multiple destinations (console for dev, JSON file for prod, third-party sink for audits) without rewriting the call sites.
19
- - You'd reach for **pino** or **winston** but want a smaller surface that's already wired into Warlock conventions (`module / action / message` shape, redaction floor, signal flush built-in).
20
- - Your team agrees that **`console.log` doesn't survive contact with production** — you need filtering, level routing, channel-specific sinks, and a redaction story before secrets leak into Slack/Datadog.
21
-
22
- Skip if your code is a throwaway script where `console.log` is genuinely fine — there's no value in adding a dependency for one-off logs.
23
-
24
- ## The mental model in one paragraph
25
-
26
- You write `log.info("auth", "login", "user signed in", { userId })`. The logger fans that single entry out to every registered channel (`ConsoleLog`, `FileLog`, `JSONFileLog`, `SentryLog`, or your custom subclass). Each channel decides whether to emit it (per-level whitelist, per-channel filter predicate, logger-wide minimum severity). Redaction runs once at the logger level and can be extended per channel — never relaxed. Buffered channels (file + JSON file) drain on flush — synchronously via `log.flushSync()` / `enableAutoFlush(['SIGINT', 'SIGTERM', 'beforeExit'])`, or asynchronously via `await log.flush()` (the only path that works for network channels like `SentryLog`). That's the whole package.
27
-
28
- ## Skills index
29
-
30
- Eleven task skills cover everything. Load the one that matches your job — most callers only ever need `logger-basics` + `configure-logger` + `pick-log-channel`.
31
-
32
- ### Foundations
33
-
34
- #### `logger-basics`
35
-
36
- Start here. The `log` singleton, the six levels (`debug` / `info` / `warn` / `error` / `success` / `fatal`), how fan-out works, the `module / action / message / context` shape every entry carries.
37
-
38
- #### `configure-logger`
39
-
40
- Wire channels at boot — `log.addChannel`, `log.setChannels`, `log.configure({ channels, autoFlushOn, redact, minLevel })`. Branch on `NODE_ENV`, replace the channel list, isolate a library's logger from the host singleton.
41
-
42
- ### Channels
43
-
44
- #### `pick-log-channel`
45
-
46
- Pick one of the four built-ins: `ConsoleLog` (terminal, colored), `FileLog` (plain `.log` on disk with rotation), `JSONFileLog` (structured JSON for aggregators — Datadog, Loki, ELK), `SentryLog` (errors + breadcrumbs to Sentry; `@sentry/node` is an optional peer).
47
-
48
- #### `ship-logs-to-sentry`
49
-
50
- The `SentryLog` channel in depth — event-vs-breadcrumb level mapping, dual init modes (reuse an existing client or pass `options`), draining via `Sentry.flush(timeout)`, behavior when the optional peer isn't installed.
51
-
52
- #### `write-custom-log-channel`
53
-
54
- Extend `LogChannel<Options>` for sinks the built-ins don't cover — Slack, HTTP endpoint, in-memory buffer, database. The lazy `init()` lifecycle (`setTimeout(0)`), the `terminal: true/false` ANSI-stripping behavior, and the difference between `flush()` (async, network) and `flushSync()` (sync, files) are subtle — read this skill before subclassing.
55
-
56
- ### Production concerns
57
-
58
- #### `redact-sensitive-log-fields`
59
-
60
- Strip secrets before they reach a sink. Logger-wide `setRedact({ paths, censor })` is the security floor; per-channel `redact` configs add paths (never remove). Dotted-glob paths (`*`, `**`); censor as string or function `(value, path) => any`.
61
-
62
- #### `filter-log-entries`
63
-
64
- Drop entries before they cost anything. Logger-wide `setMinLevel("info")` is the fast path; per-channel `levels` array + `filter` predicate for fine control.
65
-
66
- #### `flush-logs-on-shutdown`
67
-
68
- Buffered channels need explicit drain. `log.flushSync()` (sync) for file channels — also wired by `enableAutoFlush(['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK', 'SIGUSR2', 'beforeExit'])`. `await log.flush()` (async) for network/async channels like `SentryLog` — the only path that can await an HTTPS round-trip on a graceful shutdown.
69
-
70
- #### `capture-unhandled-errors`
71
-
72
- `captureAnyUnhandledRejection()` hooks `unhandledRejection` (→ `log.error("app", ...)`, process kept alive) and `uncaughtException` (→ `log.fatal("app", ...)` then `process.exit(1)` — restoring the non-zero exit the listener would otherwise suppress, so a fatal crash never becomes a silent `exit 0`; opt out with `{ exitOnUncaughtException: false }`). It flushes before exiting and falls back to `console.error` when no terminal channel is set. One call at startup.
73
-
74
- ### Ergonomics + testing
75
-
76
- #### `use-log-helpers`
77
-
78
- Two shortcuts every `Logger` exposes: `log.assert(condition, module, action, message, context?)` logs an error only when the condition is falsy (free on the happy path); `log.timer(module, action)` returns an end-function that emits `info` with a measured `durationMs`.
79
-
80
- #### `test-logging-code`
81
-
82
- Silence the logger globally in tests via `log.setChannels([])` in `setupFiles`. Assert specific entries with a capturing `LogChannel` subclass — it proves an entry was actually delivered through the pipeline (filters, redaction), not merely that a method was called, and it isolates cleanly by swapping `log.channels`.
83
-
84
- ## Built-in channels at a glance
85
-
86
- | Channel | Sink | `terminal` | Buffered |
87
- | ------------- | --------------------------- | ----------------------- | ----------------------------------------------------------------------- |
88
- | `ConsoleLog` | `process.stdout` | `true` (colors kept) | no |
89
- | `FileLog` | `.log` files | `false` (ANSI stripped) | yes (5s timer or 100-entry buffer) |
90
- | `JSONFileLog` | `.json` files | `false` (ANSI stripped) | yes (same buffering) |
91
- | `SentryLog` | Sentry events + breadcrumbs | `false` | via the Sentry SDK's own transport (`@sentry/node` is an optional peer) |
92
-
93
- `terminal: true` is the flag that decides whether the channel sees raw colored messages or stripped plain text. Custom channels: pick `true` if you write to a terminal, `false` for anything else.
94
-
95
- ## What this package deliberately doesn't do
96
-
97
- - **Distributed tracing.** Use OpenTelemetry. Logger gets you structured local logs with `module / action`; trace correlation is a different problem.
98
- - **Log aggregator integrations beyond Sentry.** `SentryLog` is bundled (with `@sentry/node` as an optional peer). For Datadog, Loki, ELK, etc., either write a custom channel that POSTs to the aggregator or use `JSONFileLog` + a sidecar (fluentbit, vector, promtail).
99
- - **Pretty-printing of arbitrary objects.** `ConsoleLog` has a `showContext` flag that runs `util.inspect` on the context object; for richer formatting, use `JSONFileLog` and view the file through your favorite viewer.
100
- - **Log analysis.** Querying / aggregating / alerting is on the sink side (Loki, ELK, Datadog).
101
-
102
- ## See also
103
-
104
- - The `warlock-conventions` topic of the `warlock-js-core` skill — the parent framework's conventions; logger is one of its foundation packages and ships transitively when you install core.
105
- - When synced via agent-kit, this `overview` topic is flattened to the front-door skill `.claude/skills/warlock-js-logger-overview/` — every cross-link above names the topic (and the owning skill, across packages) instead of a file path so it survives that flattening.