@warlock.js/logger 4.1.14 → 4.2.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 (61) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +146 -145
  4. package/cjs/index.cjs +276 -11
  5. package/cjs/index.cjs.map +1 -1
  6. package/esm/channels/console-log.d.mts +1 -1
  7. package/esm/channels/console-log.d.mts.map +1 -1
  8. package/esm/channels/console-log.mjs +4 -1
  9. package/esm/channels/console-log.mjs.map +1 -1
  10. package/esm/channels/file-log.d.mts +13 -3
  11. package/esm/channels/file-log.d.mts.map +1 -1
  12. package/esm/channels/file-log.mjs +15 -1
  13. package/esm/channels/file-log.mjs.map +1 -1
  14. package/esm/channels/index.mjs +1 -0
  15. package/esm/channels/json-file-log.d.mts +1 -1
  16. package/esm/channels/json-file-log.d.mts.map +1 -1
  17. package/esm/channels/json-file-log.mjs +1 -1
  18. package/esm/channels/json-file-log.mjs.map +1 -1
  19. package/esm/channels/sentry-log.d.mts +182 -0
  20. package/esm/channels/sentry-log.d.mts.map +1 -0
  21. package/esm/channels/sentry-log.mjs +202 -0
  22. package/esm/channels/sentry-log.mjs.map +1 -0
  23. package/esm/index.d.mts +2 -1
  24. package/esm/index.mjs +2 -1
  25. package/esm/log-channel.d.mts +9 -1
  26. package/esm/log-channel.d.mts.map +1 -1
  27. package/esm/log-channel.mjs +1 -1
  28. package/esm/log-channel.mjs.map +1 -1
  29. package/esm/logger.d.mts +32 -1
  30. package/esm/logger.d.mts.map +1 -1
  31. package/esm/logger.mjs +44 -2
  32. package/esm/logger.mjs.map +1 -1
  33. package/esm/redact/redact.d.mts +1 -1
  34. package/esm/redact/redact.d.mts.map +1 -1
  35. package/esm/redact/redact.mjs +1 -1
  36. package/esm/redact/redact.mjs.map +1 -1
  37. package/esm/types.d.mts +19 -3
  38. package/esm/types.d.mts.map +1 -1
  39. package/esm/utils/capture-unhandled-errors.d.mts +9 -1
  40. package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
  41. package/esm/utils/capture-unhandled-errors.mjs +10 -2
  42. package/esm/utils/capture-unhandled-errors.mjs.map +1 -1
  43. package/esm/utils/clear-message.d.mts +1 -1
  44. package/esm/utils/clear-message.d.mts.map +1 -1
  45. package/esm/utils/clear-message.mjs +1 -1
  46. package/esm/utils/clear-message.mjs.map +1 -1
  47. package/esm/utils/safe-json-stringify.d.mts +1 -1
  48. package/esm/utils/safe-json-stringify.d.mts.map +1 -1
  49. package/esm/utils/safe-json-stringify.mjs +1 -1
  50. package/esm/utils/safe-json-stringify.mjs.map +1 -1
  51. package/llms-full.txt +1497 -1296
  52. package/llms.txt +20 -19
  53. package/package.json +5 -2
  54. package/skills/capture-unhandled-errors/SKILL.md +103 -103
  55. package/skills/filter-log-entries/SKILL.md +120 -120
  56. package/skills/flush-logs-on-shutdown/SKILL.md +117 -91
  57. package/skills/logger-basics/SKILL.md +88 -85
  58. package/skills/overview/SKILL.md +90 -86
  59. package/skills/pick-log-channel/SKILL.md +155 -139
  60. package/skills/ship-logs-to-sentry/SKILL.md +118 -0
  61. package/skills/write-custom-log-channel/SKILL.md +190 -160
@@ -1,160 +1,190 @@
1
- ---
2
- name: write-custom-log-channel
3
- description: '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`.'
4
- ---
5
-
6
- # Custom channels — extending `LogChannel`
7
-
8
- Build a sink for any destination — Slack, a database, an HTTP endpoint — by extending the abstract `LogChannel` class.
9
-
10
- ## The 5-line minimum
11
-
12
- ```ts
13
- import { LogChannel, type LoggingData } from "@warlock.js/logger";
14
-
15
- export class NullChannel extends LogChannel {
16
- public name = "null";
17
- public log(_data: LoggingData) {}
18
- }
19
- ```
20
-
21
- Then:
22
- ```ts
23
- log.addChannel(new NullChannel());
24
- ```
25
-
26
- That's a working channel. `LogChannel` provides the scaffolding; you only need to supply `name` and `log()`.
27
-
28
- ## What `LogChannel` gives you
29
-
30
- | Thing | Who provides it |
31
- |---|---|
32
- | `name`, `description`, `terminal` | You (fields on your subclass) |
33
- | `log(data)` | **You must implement** — abstract |
34
- | `flushSync()` | You (optional — only if you buffer) |
35
- | `init()` | You (optional async hooksee below) |
36
- | `shouldBeLogged(data)` | `LogChannel` combines `levels` + `filter` |
37
- | `config<K>(key)` | `LogChannel` — merges user config with `defaultConfigurations` |
38
- | `getDateAndTimeFormat()` | `LogChannel` — returns resolved `dateFormat` |
39
-
40
- ## Complete example — SlackLog
41
-
42
- ```ts title="src/channels/slack-log.ts"
43
- import { LogChannel, type BasicLogConfigurations, type LoggingData } from "@warlock.js/logger";
44
-
45
- // `LogChannel<Options>` constrains `Options extends BasicLogConfigurations`,
46
- // so extend the base to keep the inherited levels / filter / redact options.
47
- type SlackConfig = BasicLogConfigurations & {
48
- webhookUrl: string;
49
- };
50
-
51
- export class SlackLog extends LogChannel<SlackConfig> {
52
- public name = "slack";
53
- public description = "Posts errors + warnings to a Slack webhook";
54
-
55
- public async log(data: LoggingData) {
56
- if (!this.shouldBeLogged(data)) return; // ← inherit levels + filter
57
-
58
- await fetch(this.config("webhookUrl"), {
59
- method: "POST",
60
- headers: { "Content-Type": "application/json" },
61
- body: JSON.stringify({
62
- text: `[${data.type.toUpperCase()}] [${data.module}][${data.action}]: ${data.message}`,
63
- }),
64
- });
65
- }
66
- }
67
- ```
68
-
69
- Register it alongside built-ins:
70
-
71
- ```ts
72
- log.setChannels([
73
- new ConsoleLog(),
74
- new FileLog({ chunk: "daily" }),
75
- new SlackLog({
76
- webhookUrl: process.env.SLACK_WEBHOOK_URL!,
77
- levels: ["error", "warn"],
78
- }),
79
- ]);
80
- ```
81
-
82
- ## The `init()` hook
83
-
84
- Override `protected async init()` for one-time setup — open a socket, connect to a DB, prepare a write stream. Runs automatically after construction (inside a `setTimeout(0)`); `isInitialized` flips to `true` once resolved.
85
-
86
- ```ts
87
- export class DatabaseLog extends LogChannel<
88
- BasicLogConfigurations & { connectionString: string }
89
- > {
90
- public name = "database";
91
- private client!: SomeDbClient;
92
-
93
- protected async init() {
94
- this.client = await SomeDbClient.connect(this.config("connectionString"));
95
- }
96
-
97
- public async log(data: LoggingData) {
98
- if (!this.shouldBeLogged(data)) return;
99
- await this.client.insert("logs", data);
100
- }
101
- }
102
- ```
103
-
104
- ## Implementing `flushSync()`
105
-
106
- Only if your channel buffers. Signature: `flushSync?(): void`. Synchronous — no `await`, no promises.
107
-
108
- ```ts
109
- export class BatchHttpLog extends LogChannel<BasicLogConfigurations & { url: string }> {
110
- public name = "batch-http";
111
- private buffer: LoggingData[] = [];
112
-
113
- public log(data: LoggingData) {
114
- if (!this.shouldBeLogged(data)) return;
115
- this.buffer.push(data);
116
- if (this.buffer.length >= 100) void this.drain();
117
- }
118
-
119
- public flushSync() {
120
- // Synchronous HTTP — use `node:http` or `XMLHttpRequest` polyfill.
121
- // If sync HTTP isn't possible, at least dump the buffer to disk here
122
- // so a follow-up async drain can recover it next boot.
123
- }
124
-
125
- private async drain() { /* async post to this.config("url") */ }
126
- }
127
- ```
128
-
129
- ## The `terminal` property
130
-
131
- - `terminal = true` (ConsoleLog default) → the logger passes the **original** message, ANSI codes intact.
132
- - `terminal = false` (base default, all file channels) the logger passes a shallow-cloned copy whose `message` has ANSI codes stripped.
133
-
134
- Set `terminal = true` on a channel only if its output is a TTY that should render colors.
135
-
136
- ## `LogContract` the minimal interface
137
-
138
- If you don't want anything `LogChannel` provides (level filtering, config merging), implement `LogContract` directly:
139
-
140
- ```ts
141
- import type { LogContract, LoggingData } from "@warlock.js/logger";
142
-
143
- class MinimalSlack implements LogContract {
144
- public name = "slack";
145
-
146
- public async log(data: LoggingData) {
147
- if (data.type !== "error") return;
148
- await fetch(process.env.SLACK_WEBHOOK!, { /* ... */ });
149
- }
150
- }
151
- ```
152
-
153
- Prefer extending `LogChannel` unless you have a concrete reason not to — the level/filter plumbing is worth keeping.
154
-
155
- ## Don't do
156
-
157
- - Don't mutate `data` inside `log()`. Later channels see the mutation if the logger passes the same reference.
158
- - Don't throw synchronously from `log()`. The logger fires it without awaiting; an unhandled rejection takes down the process (unless `captureAnyUnhandledRejection` is wired up — and then it's embarrassing to be the cause).
159
- - Don't block the event loop. `log()` may be sync or async; if your work takes >100ms, make it async and return the promise.
160
- - Don't forget `shouldBeLogged(data)` at the top of `log()` — or your channel silently ignores `levels` / `filter` config.
1
+ ---
2
+ name: write-custom-log-channel
3
+ description: 'Extend the abstract LogChannel class for custom sinks — Slack, database, HTTP endpoint, in-memory buffer. Triggers: `LogChannel`, `LogContract`, `LoggingData`, `shouldBeLogged`, `init`, `flush`, `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`.'
4
+ ---
5
+
6
+ # Custom channels — extending `LogChannel`
7
+
8
+ Build a sink for any destination — Slack, a database, an HTTP endpoint — by extending the abstract `LogChannel` class.
9
+
10
+ ## The 5-line minimum
11
+
12
+ ```ts
13
+ import { LogChannel, type LoggingData } from "@warlock.js/logger";
14
+
15
+ export class NullChannel extends LogChannel {
16
+ public name = "null";
17
+ public log(_data: LoggingData) {}
18
+ }
19
+ ```
20
+
21
+ Then:
22
+ ```ts
23
+ log.addChannel(new NullChannel());
24
+ ```
25
+
26
+ That's a working channel. `LogChannel` provides the scaffolding; you only need to supply `name` and `log()`.
27
+
28
+ ## What `LogChannel` gives you
29
+
30
+ | Thing | Who provides it |
31
+ |---|---|
32
+ | `name`, `description`, `terminal` | You (fields on your subclass) |
33
+ | `log(data)` | **You must implement** — abstract |
34
+ | `flushSync()` | You (optional sync drain — only if you buffer) |
35
+ | `flush()` | You (optional async drainonly if you buffer over async I/O) |
36
+ | `init()` | You (optional async hook see below) |
37
+ | `shouldBeLogged(data)` | `LogChannel` — combines `levels` + `filter` |
38
+ | `config<K>(key)` | `LogChannel` — merges user config with `defaultConfigurations` |
39
+ | `getDateAndTimeFormat()` | `LogChannel` — returns resolved `dateFormat` |
40
+
41
+ ## Complete example — SlackLog
42
+
43
+ ```ts title="src/channels/slack-log.ts"
44
+ import { LogChannel, type BasicLogConfigurations, type LoggingData } from "@warlock.js/logger";
45
+
46
+ // `LogChannel<Options>` constrains `Options extends BasicLogConfigurations`,
47
+ // so extend the base to keep the inherited levels / filter / redact options.
48
+ type SlackConfig = BasicLogConfigurations & {
49
+ webhookUrl: string;
50
+ };
51
+
52
+ export class SlackLog extends LogChannel<SlackConfig> {
53
+ public name = "slack";
54
+ public description = "Posts errors + warnings to a Slack webhook";
55
+
56
+ public async log(data: LoggingData) {
57
+ if (!this.shouldBeLogged(data)) return; // ← inherit levels + filter
58
+
59
+ await fetch(this.config("webhookUrl"), {
60
+ method: "POST",
61
+ headers: { "Content-Type": "application/json" },
62
+ body: JSON.stringify({
63
+ text: `[${data.type.toUpperCase()}] [${data.module}][${data.action}]: ${data.message}`,
64
+ }),
65
+ });
66
+ }
67
+ }
68
+ ```
69
+
70
+ Register it alongside built-ins:
71
+
72
+ ```ts
73
+ log.setChannels([
74
+ new ConsoleLog(),
75
+ new FileLog({ chunk: "daily" }),
76
+ new SlackLog({
77
+ webhookUrl: process.env.SLACK_WEBHOOK_URL!,
78
+ levels: ["error", "warn"],
79
+ }),
80
+ ]);
81
+ ```
82
+
83
+ ## The `init()` hook
84
+
85
+ Override `protected async init()` for one-time setup — open a socket, connect to a DB, prepare a write stream. Runs automatically after construction (inside a `setTimeout(0)`); `isInitialized` flips to `true` once resolved.
86
+
87
+ ```ts
88
+ export class DatabaseLog extends LogChannel<
89
+ BasicLogConfigurations & { connectionString: string }
90
+ > {
91
+ public name = "database";
92
+ private client!: SomeDbClient;
93
+
94
+ protected async init() {
95
+ this.client = await SomeDbClient.connect(this.config("connectionString"));
96
+ }
97
+
98
+ public async log(data: LoggingData) {
99
+ if (!this.shouldBeLogged(data)) return;
100
+ await this.client.insert("logs", data);
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Implementing `flushSync()`
106
+
107
+ Only if your channel buffers. Signature: `flushSync?(): void`. Synchronous — no `await`, no promises.
108
+
109
+ ```ts
110
+ export class BatchHttpLog extends LogChannel<BasicLogConfigurations & { url: string }> {
111
+ public name = "batch-http";
112
+ private buffer: LoggingData[] = [];
113
+
114
+ public log(data: LoggingData) {
115
+ if (!this.shouldBeLogged(data)) return;
116
+ this.buffer.push(data);
117
+ if (this.buffer.length >= 100) void this.drain();
118
+ }
119
+
120
+ public flushSync() {
121
+ // Synchronous HTTP use `node:http` or `XMLHttpRequest` polyfill.
122
+ // If sync HTTP isn't possible, at least dump the buffer to disk here
123
+ // so a follow-up async drain can recover it next boot.
124
+ }
125
+
126
+ private async drain() { /* async post to this.config("url") */ }
127
+ }
128
+ ```
129
+
130
+ ## Implementing `flush()` — async drain
131
+
132
+ `flushSync()` is synchronous, so a channel that delivers over the network (or any async I/O) can't drain that way. Implement `flush(): Promise<void>` so `await log.flush()` drains it on a graceful shutdown:
133
+
134
+ ```ts
135
+ export class BatchHttpLog extends LogChannel<BasicLogConfigurations & { url: string }> {
136
+ public name = "batch-http";
137
+ private buffer: LoggingData[] = [];
138
+
139
+ public log(data: LoggingData) {
140
+ if (!this.shouldBeLogged(data)) return;
141
+ this.buffer.push(data);
142
+ if (this.buffer.length >= 100) void this.drain();
143
+ }
144
+
145
+ public async flush() {
146
+ await this.drain(); // awaited by log.flush() on shutdown
147
+ }
148
+
149
+ private async drain() {
150
+ if (this.buffer.length === 0) return;
151
+ const batch = this.buffer.splice(0);
152
+ await fetch(this.config("url"), { method: "POST", body: JSON.stringify(batch) });
153
+ }
154
+ }
155
+ ```
156
+
157
+ `Logger.flush()` isolates each channel — a rejecting `flush()` won't break the others — but handle your own failures so a shutdown drain doesn't silently drop the batch. Implement `flushSync()` too (e.g. dump to disk) when you also want a best-effort sync path for `autoFlushOn`.
158
+
159
+ ## The `terminal` property
160
+
161
+ - `terminal = true` (ConsoleLog default) → the logger passes the **original** message, ANSI codes intact.
162
+ - `terminal = false` (base default, all file channels) → the logger passes a shallow-cloned copy whose `message` has ANSI codes stripped.
163
+
164
+ Set `terminal = true` on a channel only if its output is a TTY that should render colors.
165
+
166
+ ## `LogContract` — the minimal interface
167
+
168
+ If you don't want anything `LogChannel` provides (level filtering, config merging), implement `LogContract` directly:
169
+
170
+ ```ts
171
+ import type { LogContract, LoggingData } from "@warlock.js/logger";
172
+
173
+ class MinimalSlack implements LogContract {
174
+ public name = "slack";
175
+
176
+ public async log(data: LoggingData) {
177
+ if (data.type !== "error") return;
178
+ await fetch(process.env.SLACK_WEBHOOK!, { /* ... */ });
179
+ }
180
+ }
181
+ ```
182
+
183
+ Prefer extending `LogChannel` unless you have a concrete reason not to — the level/filter plumbing is worth keeping.
184
+
185
+ ## Don't do
186
+
187
+ - Don't mutate `data` inside `log()`. Later channels see the mutation if the logger passes the same reference.
188
+ - Don't throw synchronously from `log()`. The logger fires it without awaiting; an unhandled rejection takes down the process (unless `captureAnyUnhandledRejection` is wired up — and then it's embarrassing to be the cause).
189
+ - Don't block the event loop. `log()` may be sync or async; if your work takes >100ms, make it async and return the promise.
190
+ - Don't forget `shouldBeLogged(data)` at the top of `log()` — or your channel silently ignores `levels` / `filter` config.