@warlock.js/logger 4.1.15 → 4.2.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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +146 -145
- package/cjs/index.cjs +276 -11
- package/cjs/index.cjs.map +1 -1
- package/esm/channels/console-log.d.mts +1 -1
- package/esm/channels/console-log.d.mts.map +1 -1
- package/esm/channels/console-log.mjs +4 -1
- package/esm/channels/console-log.mjs.map +1 -1
- package/esm/channels/file-log.d.mts +13 -3
- package/esm/channels/file-log.d.mts.map +1 -1
- package/esm/channels/file-log.mjs +15 -1
- package/esm/channels/file-log.mjs.map +1 -1
- package/esm/channels/index.mjs +1 -0
- package/esm/channels/json-file-log.d.mts +1 -1
- package/esm/channels/json-file-log.d.mts.map +1 -1
- package/esm/channels/json-file-log.mjs +1 -1
- package/esm/channels/json-file-log.mjs.map +1 -1
- package/esm/channels/sentry-log.d.mts +182 -0
- package/esm/channels/sentry-log.d.mts.map +1 -0
- package/esm/channels/sentry-log.mjs +202 -0
- package/esm/channels/sentry-log.mjs.map +1 -0
- package/esm/index.d.mts +2 -1
- package/esm/index.mjs +2 -1
- package/esm/log-channel.d.mts +9 -1
- package/esm/log-channel.d.mts.map +1 -1
- package/esm/log-channel.mjs +1 -1
- package/esm/log-channel.mjs.map +1 -1
- package/esm/logger.d.mts +32 -1
- package/esm/logger.d.mts.map +1 -1
- package/esm/logger.mjs +44 -2
- package/esm/logger.mjs.map +1 -1
- package/esm/redact/redact.d.mts +1 -1
- package/esm/redact/redact.d.mts.map +1 -1
- package/esm/redact/redact.mjs +1 -1
- package/esm/redact/redact.mjs.map +1 -1
- package/esm/types.d.mts +19 -3
- package/esm/types.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.d.mts +9 -1
- package/esm/utils/capture-unhandled-errors.d.mts.map +1 -1
- package/esm/utils/capture-unhandled-errors.mjs +10 -2
- package/esm/utils/capture-unhandled-errors.mjs.map +1 -1
- package/esm/utils/clear-message.d.mts +1 -1
- package/esm/utils/clear-message.d.mts.map +1 -1
- package/esm/utils/clear-message.mjs +1 -1
- package/esm/utils/clear-message.mjs.map +1 -1
- package/esm/utils/safe-json-stringify.d.mts +1 -1
- package/esm/utils/safe-json-stringify.d.mts.map +1 -1
- package/esm/utils/safe-json-stringify.mjs +1 -1
- package/esm/utils/safe-json-stringify.mjs.map +1 -1
- package/llms-full.txt +1497 -1296
- package/llms.txt +20 -19
- package/package.json +5 -2
- package/skills/capture-unhandled-errors/SKILL.md +103 -103
- package/skills/filter-log-entries/SKILL.md +120 -120
- package/skills/flush-logs-on-shutdown/SKILL.md +117 -91
- package/skills/logger-basics/SKILL.md +88 -85
- package/skills/overview/SKILL.md +90 -86
- package/skills/pick-log-channel/SKILL.md +155 -139
- package/skills/ship-logs-to-sentry/SKILL.md +118 -0
- 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
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
public
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
new
|
|
75
|
-
new
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 drain — only 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.
|