@warlock.js/logger 5.29.0 → 5.31.0

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 (81) hide show
  1. package/CHANGELOG.md +48 -2
  2. package/README.md +14 -17
  3. package/cjs/index.cjs +569 -57
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/channels/console-log.d.mts +1 -1
  6. package/esm/channels/console-log.d.mts.map +1 -1
  7. package/esm/channels/console-log.mjs +1 -1
  8. package/esm/channels/console-log.mjs.map +1 -1
  9. package/esm/channels/file-log.d.mts +129 -13
  10. package/esm/channels/file-log.d.mts.map +1 -1
  11. package/esm/channels/file-log.mjs +204 -23
  12. package/esm/channels/file-log.mjs.map +1 -1
  13. package/esm/channels/file-name-pattern.d.mts +16 -0
  14. package/esm/channels/file-name-pattern.d.mts.map +1 -0
  15. package/esm/channels/file-name-pattern.mjs +41 -0
  16. package/esm/channels/file-name-pattern.mjs.map +1 -0
  17. package/esm/channels/index.mjs +1 -1
  18. package/esm/channels/json-file-log.d.mts +1 -1
  19. package/esm/channels/json-file-log.d.mts.map +1 -1
  20. package/esm/channels/json-file-log.mjs +25 -6
  21. package/esm/channels/json-file-log.mjs.map +1 -1
  22. package/esm/channels/sentry-log.d.mts +1 -1
  23. package/esm/channels/sentry-log.d.mts.map +1 -1
  24. package/esm/channels/sentry-log.mjs +1 -1
  25. package/esm/channels/sentry-log.mjs.map +1 -1
  26. package/esm/index.d.mts +8 -5
  27. package/esm/index.mjs +5 -3
  28. package/esm/log-channel.d.mts +24 -2
  29. package/esm/log-channel.d.mts.map +1 -1
  30. package/esm/log-channel.mjs +23 -1
  31. package/esm/log-channel.mjs.map +1 -1
  32. package/esm/logger.d.mts +78 -18
  33. package/esm/logger.d.mts.map +1 -1
  34. package/esm/logger.mjs +163 -22
  35. package/esm/logger.mjs.map +1 -1
  36. package/esm/redact/default-keys.d.mts +1 -1
  37. package/esm/redact/default-keys.d.mts.map +1 -1
  38. package/esm/redact/default-keys.mjs +1 -1
  39. package/esm/redact/default-keys.mjs.map +1 -1
  40. package/esm/redact/escape-log-value.d.mts +26 -0
  41. package/esm/redact/escape-log-value.d.mts.map +1 -0
  42. package/esm/redact/escape-log-value.mjs +37 -0
  43. package/esm/redact/escape-log-value.mjs.map +1 -0
  44. package/esm/redact/index.mjs +2 -0
  45. package/esm/redact/mask-email.d.mts +40 -0
  46. package/esm/redact/mask-email.d.mts.map +1 -0
  47. package/esm/redact/mask-email.mjs +70 -0
  48. package/esm/redact/mask-email.mjs.map +1 -0
  49. package/esm/redact/redact.d.mts +1 -1
  50. package/esm/redact/redact.d.mts.map +1 -1
  51. package/esm/redact/redact.mjs +1 -1
  52. package/esm/redact/redact.mjs.map +1 -1
  53. package/esm/types.d.mts +10 -2
  54. package/esm/types.d.mts.map +1 -1
  55. package/esm/utils/capture-unhandled-errors.d.mts +1 -1
  56. package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
  57. package/esm/utils/capture-unhandled-errors.mjs +1 -1
  58. package/esm/utils/capture-unhandled-errors.mjs.map +1 -1
  59. package/esm/utils/clear-message.d.mts +1 -1
  60. package/esm/utils/clear-message.d.mts.map +1 -1
  61. package/esm/utils/clear-message.mjs +1 -1
  62. package/esm/utils/clear-message.mjs.map +1 -1
  63. package/esm/utils/safe-json-stringify.d.mts +1 -1
  64. package/esm/utils/safe-json-stringify.d.mts.map +1 -1
  65. package/esm/utils/safe-json-stringify.mjs +1 -1
  66. package/esm/utils/safe-json-stringify.mjs.map +1 -1
  67. package/llms-full.txt +247 -147
  68. package/llms.txt +2 -2
  69. package/package.json +4 -3
  70. package/skills/capture-unhandled-errors/SKILL.md +9 -10
  71. package/skills/configure-logger/SKILL.md +7 -10
  72. package/skills/filter-log-entries/SKILL.md +2 -2
  73. package/skills/flush-logs-on-shutdown/SKILL.md +34 -15
  74. package/skills/index.md +4 -1
  75. package/skills/logger-basics/SKILL.md +21 -24
  76. package/skills/overview/SKILL.md +17 -6
  77. package/skills/pick-log-channel/SKILL.md +36 -24
  78. package/skills/redact-sensitive-log-fields/SKILL.md +48 -18
  79. package/skills/ship-logs-to-sentry/SKILL.md +18 -18
  80. package/skills/test-logging-code/SKILL.md +4 -2
  81. package/skills/write-custom-log-channel/SKILL.md +51 -18
package/CHANGELOG.md CHANGED
@@ -4,6 +4,51 @@ 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.31.0 - 2026-10-18
8
+
9
+ ### Upgrading
10
+
11
+ - **BREAKING:** `FileLog`/`JSONFileLog` dated file names are ISO dates that sort by name: `chunk: "daily"` writes `YYYY-MM-DD.log` (was `DD-MM-YYYY.log`) and `chunk: "hourly"` writes `YYYY-MM-DD-HH.log` (was `DD-MM-YYYY-HH-00-00-am.log`). Size-rotated archives default to `<file name>-YYYY-MM-DD-<epoch ms>`. After upgrading, a running daily channel starts a new file under the new name. To keep the old names, set `fileName: "{DD}-{MM}-{YYYY}"` (daily) and `rotateFileName: "{DD}-{MM}-{YYYY}"`. Tools that find logs by name (shippers, cron cleanups) need the new pattern; `retention` does not remove files with the old names.
12
+ - `rotateFileName` is now a `{YYYY}`/`{MM}`/`{DD}`/`{HH}`/`{mm}`/`{name}` pattern or a function of the rotation date, not a Day.js format. A value without tokens is used as is, as before; a Day.js format such as `"DD-MM-YYYY"` was always written literally and still is — use `"{DD}-{MM}-{YYYY}"`.
13
+
14
+ ### Added
15
+
16
+ - `FileLog` `fileName` option: a pattern (`"app-{YYYY}-{MM}-{DD}"`, tokens `{YYYY}` `{MM}` `{DD}` `{HH}` `{mm}` `{name}` in local time) or a `(date) => string` function, evaluated at every write; it replaces the chunk's default name. `rotateFileName` takes the same forms and is rendered at each rotation (a getter passed in the config was read once, at construction, so every archive kept the boot date). Exported `FileNameOption`, `FileLogRetention`, `DAILY_FILE_NAME`, `HOURLY_FILE_NAME` and `ROTATE_FILE_NAME`.
17
+ - `FileLog` `retention: { maxFiles?, maxAgeDays?, match? }`: removes old log files on boot and whenever the channel starts a new file (a size rotation or a dated name rolling over), plus `channel.prune()` on demand. It only considers files in the log directory (each group directory with `groupBy`) whose names match the channel's file name pattern, keeps the file being written, and reports failures to `onChannelError` instead of throwing. A function `fileName` needs `retention.match`. Invalid `maxFiles`/`maxAgeDays` throw a `TypeError` at construction.
18
+ - `LogChannel.reportError(error, entry)` and `setErrorReporter(reporter)` (type `ChannelErrorReporter`): a channel reports failures outside `log()` to the `onChannelError` handler of the logger it was added to (`addChannel`, `setChannels`, `configure({ channels })`), or one stderr line without one.
19
+ - `escapeLogValue(value, { maxLength? })` makes client-controlled text safe inside a log line: CR/LF/tab become visible `\r`/`\n`/`\t`, other control characters (NUL, DEL, C1, U+2028/U+2029) become `\uXXXX`, ANSI escape sequences are removed, and `maxLength` cuts with a `…[truncated]` marker. A user agent or form field can no longer forge entries in console or file logs.
20
+ - Skills demonstrate email masking inside text, channel-error observation and bounded asynchronous log drains.
21
+
22
+ ### Fixed
23
+
24
+ - With `groupBy`, size rotation renames the group file that grew too large instead of the ungrouped top-level file.
25
+ - A file name that throws while a buffer is written (a `fileName` function, or an overridden `fileName` getter) is reported as a failed write instead of leaving the channel stuck in its writing state; later writes go through.
26
+
27
+ ## 5.30.0 - 2026-10-16
28
+
29
+ ### Upgrading
30
+
31
+ - **BREAKING (types):** `log.info/warn/error/debug/success/fatal/log/assert` and the function `timer()` returns now return the logger synchronously instead of `Promise<this>`. Remove `await` before log calls (it is harmless but pointless) and any `.then()` chained on one; code that needs the write to finish calls `await log.flush()`. `warlock upgrade --from 5.29 --to 5.31.0` removes the `await`s and lists chained `.then()` calls (codemod `logger-sync-calls`).
32
+
33
+ ### Changed
34
+
35
+ - Logging is fire-and-forget: level methods no longer return a promise, so `@typescript-eslint/no-floating-promises` no longer flags every call site.
36
+ - `log.flush()` drains every channel concurrently: each waits for its own in-flight async deliveries (including entries logged while the flush runs), then runs its `flush()`, so a hung network channel no longer holds up the file channel.
37
+ - `log.flush({ timeout })` is bounded: it resolves after `timeout` ms (default 5000, `DEFAULT_FLUSH_TIMEOUT_MS`; `Infinity` waits without a limit) and reports each channel still draining to the channel error handler. Before, one delivery that never settled made `flush()` (and a graceful shutdown awaiting it) wait forever.
38
+ - `maskEmailsInText` cuts text longer than `MASK_EMAILS_MAX_LENGTH` (16,384 characters) and marks the cut, so masking a huge cache key or message has a bounded cost.
39
+
40
+ ### Added
41
+
42
+ - `log.onChannelError(handler)` / `configure({ onChannelError })` and the `ChannelErrorHandler` type: receives a channel's sync throw or async rejection from `log()`, a redaction that threw, or a timed-out `flush()`. Default: one stderr line. The handler may be async; a rejection is swallowed.
43
+ - `maskEmail(address)` and `maskEmailsInText(text)`: mask email addresses (`m***@example.com`) inside message strings and cache keys, which key-based redaction cannot reach. Core's mail logs and the cache drivers' key logs use them.
44
+
45
+ ### Fixed
46
+
47
+ - A channel whose `log()` rejected became an unhandled rejection (looping through `captureAnyUnhandledRejection`), and one that threw synchronously stopped delivery to the channels after it; both are now isolated and reported to the channel error handler.
48
+ - A redaction that threw (a `censor` function that fails on an unexpected value) threw at the call site and skipped the remaining channels. A channel's own redaction failure is now isolated like any channel failure; a logger-wide one drops the entry (it is never delivered unredacted) and is reported to the channel error handler.
49
+ - An async `onChannelError` handler that rejected became an unhandled rejection.
50
+ - **Security:** `maskEmailsInText` backtracked quadratically on long runs of address-like characters with no domain (about 11 s on 75 KB), and it runs on cache keys built from request input. Its pattern is now bounded and linear. It also masks addresses with letters from any script (`jöhn@exämple.de`), and `maskEmail` no longer keeps half of a surrogate pair.
51
+
7
52
  ## 5.29.0 - 2026-10-01
8
53
 
9
54
  ### Changed
@@ -75,6 +120,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
75
120
  ### Changed
76
121
 
77
122
  - Lockstep release maintenance and dependency refresh.
123
+
78
124
  ## 5.20.0 - 2026-09-24
79
125
 
80
126
  ### Changed
@@ -164,7 +210,7 @@ changelogs.
164
210
 
165
211
  ### Security
166
212
 
167
- - **Secrets are now redacted by default. This is a behavior change — logs that previously showed these values in cleartext will now show `[REDACTED]`.** Redaction used to be a *tool* (`redact.paths`, entirely opt-in): unless an application configured it, a `password` in `context`, an `authorization` header, or an `apiKey` on a logged `Error` reached every sink — console, log file, JSON log file, Sentry — verbatim. Protection existed only where every call site had been configured correctly, with no signal when one hadn't. It is now a *default*.
213
+ - **Secrets are now redacted by default. This is a behavior change — logs that previously showed these values in cleartext will now show `[REDACTED]`.** Redaction used to be a _tool_ (`redact.paths`, entirely opt-in): unless an application configured it, a `password` in `context`, an `authorization` header, or an `apiKey` on a logged `Error` reached every sink — console, log file, JSON log file, Sentry — verbatim. Protection existed only where every call site had been configured correctly, with no signal when one hadn't. It is now a _default_.
168
214
 
169
215
  A built-in denylist (`DEFAULT_REDACT_KEYS`) censors matching keys at any depth of `context`, `message`, and an `Error`'s own enumerable properties, applied at the same logger-wide choke point as `redact.paths` — so every channel inherits it, including custom ones. The set covers passwords, shared secrets, tokens (access/refresh/id/CSRF/session), API and private keys, HTTP credential headers (`authorization`, `x-api-key`, `cookie`, `set-cookie`), session IDs, connection strings, and high-sensitivity financial PII (card numbers, CVV, SSN, IBAN).
170
216
 
@@ -179,7 +225,7 @@ changelogs.
179
225
 
180
226
  Paths are evaluated before keys, so a function `censor` on an explicit path still receives the original value rather than a mask, and that leaf is not censored twice.
181
227
 
182
- Known residual gaps, unchanged by this release and documented on `applyRedact`: secrets interpolated into a `message` *string* (`` `token=${t}` ``) cannot be reached by either matcher; `Map` / `Set` / `Buffer` contents are not traversed; and properties exposed only via getters or non-enumerable descriptors are not walked, so an HTTP client that hides its request config behind a getter can still slip through. Enumerable ones — axios's `.config` / `.response`, which carry the outgoing `Authorization` header — *are* now covered.
228
+ Known residual gaps, unchanged by this release and documented on `applyRedact`: secrets interpolated into a `message` _string_ (`` `token=${t}` ``) cannot be reached by either matcher; `Map` / `Set` / `Buffer` contents are not traversed; and properties exposed only via getters or non-enumerable descriptors are not walked, so an HTTP client that hides its request config behind a getter can still slip through. Enumerable ones — axios's `.config` / `.response`, which carry the outgoing `Authorization` header — _are_ now covered.
183
229
 
184
230
  ### Fixed
185
231
 
package/README.md CHANGED
@@ -23,13 +23,10 @@ yarn add @warlock.js/logger
23
23
  ```ts
24
24
  import { log, ConsoleLog, FileLog } from "@warlock.js/logger";
25
25
 
26
- log.setChannels([
27
- new ConsoleLog(),
28
- new FileLog({ chunk: "daily" }),
29
- ]);
26
+ log.setChannels([new ConsoleLog(), new FileLog({ chunk: "daily" })]);
30
27
 
31
- await log.info("users", "register", "New user created");
32
- await log.error("payments", "charge", new Error("Card declined"));
28
+ log.info("users", "register", "New user created");
29
+ log.error("payments", "charge", new Error("Card declined"));
33
30
  ```
34
31
 
35
32
  The logger starts with no channels — nothing is printed or written until you register at least one.
@@ -39,15 +36,15 @@ The logger starts with no channels — nothing is printed or written until you r
39
36
  Every call carries three pieces of context. Modules and actions become searchable keys in file and JSON channels:
40
37
 
41
38
  ```ts
42
- await log.info("auth", "login", "User signed in");
43
- await log.warn("api", "rateLimitApproaching", "80% of quota used");
44
- await log.success("payments", "captured", "Payment of $49.99 captured");
39
+ log.info("auth", "login", "User signed in");
40
+ log.warn("api", "rateLimitApproaching", "80% of quota used");
41
+ log.success("payments", "captured", "Payment of $49.99 captured");
45
42
  ```
46
43
 
47
44
  Pass an object instead of positional args when you need `context` metadata:
48
45
 
49
46
  ```ts
50
- await log.error({
47
+ log.error({
51
48
  module: "orders",
52
49
  action: "checkout",
53
50
  message: "Card declined",
@@ -61,12 +58,12 @@ await log.error({
61
58
 
62
59
  ## Built-in channels
63
60
 
64
- | Channel | Name | Purpose |
65
- |---|---|---|
66
- | `ConsoleLog` | `"console"` | Colorized terminal output |
67
- | `FileLog` | `"file"` | Plain-text files, optional chunking + rotation + grouping |
68
- | `JSONFileLog` | `"fileJson"` | Structured JSON files — ideal for aggregators |
69
- | `SentryLog` | `"sentry"` | Forwards to Sentry (events + breadcrumbs); needs the optional `@sentry/node` peer |
61
+ | Channel | Name | Purpose |
62
+ | ------------- | ------------ | --------------------------------------------------------------------------------- |
63
+ | `ConsoleLog` | `"console"` | Colorized terminal output |
64
+ | `FileLog` | `"file"` | Plain-text files, optional chunking + rotation + grouping |
65
+ | `JSONFileLog` | `"fileJson"` | Structured JSON files — ideal for aggregators |
66
+ | `SentryLog` | `"sentry"` | Forwards to Sentry (events + breadcrumbs); needs the optional `@sentry/node` peer |
70
67
 
71
68
  All channels share the `BasicLogConfigurations` options (`levels`, `filter`, `dateFormat`); `FileLog` / `JSONFileLog` add storage, chunking, rotation, and grouping options, and `SentryLog` adds `client` / `options` / `eventLevels`.
72
69
 
@@ -74,7 +71,7 @@ All channels share the `BasicLogConfigurations` options (`levels`, `filter`, `da
74
71
 
75
72
  The package exports one default singleton and one class:
76
73
 
77
- - **`log`** — a pre-instantiated `Logger`. Day-to-day logging *and* configuration both go through it: `log.info(...)`, `log.configure(...)`, `log.flush()` / `log.flushSync()`, `log.addChannel(...)`.
74
+ - **`log`** — a pre-instantiated `Logger`. Day-to-day logging _and_ configuration both go through it: `log.info(...)`, `log.configure(...)`, `log.flush()` / `log.flushSync()`, `log.addChannel(...)`.
78
75
  - **`Logger`** — the class. Use it when you need an isolated logger (libraries, sandboxes, parallel test suites).
79
76
 
80
77
  `log` is a `Logger` instance, not a function — every level shortcut and configuration method is reachable on it. The bare-callable `log(data)` form was removed; use `log.log(data)` for the data-object form, or `log.info(...)` / `log.error(...)` / etc. for the positional form.