@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,139 +1,155 @@
1
- ---
2
- name: pick-log-channel
3
- description: '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`.'
4
- ---
5
-
6
- # Channels — which one to pick and how to configure it
7
-
8
- Three built-in channels. A channel is a destination for a log entry — the logger fans out every entry to every registered channel in parallel.
9
-
10
- ## The decision
11
-
12
- | Need | Pick |
13
- |---|---|
14
- | Local dev, colored output in the terminal | `ConsoleLog` |
15
- | Plain text `.log` files on disk — humans read them | `FileLog` |
16
- | Structured `.json` files — a log aggregator (Loki / Datadog / Elastic) reads them | `JSONFileLog` |
17
-
18
- Most production setups use **two** channels: `ConsoleLog` + one file channel. Dev uses `ConsoleLog` only.
19
-
20
- ## `ConsoleLog`
21
-
22
- Zero config. Colored, icon-prefixed lines to the terminal.
23
-
24
- ```ts
25
- import { ConsoleLog } from "@warlock.js/logger";
26
-
27
- new ConsoleLog();
28
- // (2024-03-15T10:22:00.000Z) [auth] [hashPassword] Hashing started
29
- // (2024-03-15T10:22:01.482Z) [users] [register] New user created
30
- // (2024-03-15T10:22:03.111Z) [payments] [charge] Card declined
31
- ```
32
-
33
- Properties:
34
- - `name = "console"`, `terminal = true`
35
- - Accepts `ConsoleLogConfig` `levels`, `filter`, `dateFormat`, `showContext`, `contextDepth`
36
- - If `message` is an object, a second `console.log(message)` is issued so Node's inspector can expand it
37
-
38
- ### Showing context
39
-
40
- By default `ConsoleLog` drops the `context` payload (the file/JSON channels still keep it). Flip `showContext: true` to render it on a second line — useful in development:
41
-
42
- ```ts
43
- new ConsoleLog({ showContext: true });
44
-
45
- log.info("payments", "charge", "card declined", { userId: 42, amount: 1999 });
46
- // ℹ (…) [payments] [charge] card declined
47
- // { userId: 42, amount: 1999 }
48
- ```
49
-
50
- Tune `contextDepth` (default `4`) to clamp how deep `util.inspect` recurses into nested objects.
51
-
52
- ## `FileLog`
53
-
54
- Plain text. Buffers in memory, flushes to disk periodically.
55
-
56
- ```ts
57
- import { FileLog } from "@warlock.js/logger";
58
-
59
- new FileLog({
60
- storagePath: "./storage/logs", // default: process.cwd() + "/storage/logs"
61
- name: "app", // default: "app"
62
- extension: "log", // default: "log"
63
- chunk: "daily", // "single" (default) | "daily" | "hourly"
64
- rotate: true, // default: true
65
- maxFileSize: 10 * 1024 * 1024, // default: 10MB — triggers rotation
66
- maxMessagesToWrite: 100, // default: 100flush threshold
67
- groupBy: ["level", "module"], // optional subdirectory nesting
68
- });
69
- ```
70
-
71
- Line format: `[date time] [level] [module][action]: message` — or a `[trace]` block when `message` is an `Error`.
72
-
73
- ### Key gotchas
74
-
75
- - **Buffers!** Messages sit in memory until either `maxMessagesToWrite` is reached, 5 seconds pass, or `flushSync()` is called. A process that crashes without flushing loses buffered entries.
76
- - **`chunk: "daily"` picks a filename per day.** File name becomes `DD-MM-YYYY.log`. Combined with `rotate: true`, rotated archives get `Date.now()` suffixed.
77
- - **`groupBy` nests directories.** `groupBy: ["level", "module"]` produces `storage/logs/error/payments/app.log`. Order matters.
78
- - **Dispose channels you discard.** A live `FileLog` keeps a 5-second flush interval running. If you swap the channel list at runtime (reconfigure the logger), call `channel.dispose()` on the old instance — it clears that timer and drains the buffer one last time. Skipping it leaks one timer per discarded channel and keeps the event loop alive. (Channels that live for the whole process don't need this — process exit clears the timer.)
79
-
80
- ## `JSONFileLog`
81
-
82
- Subclass of `FileLog` — same buffering, chunking, rotation, grouping. Output is a JSON object with a `messages` array:
83
-
84
- ```json
85
- {
86
- "messages": [
87
- {
88
- "content": "Card declined",
89
- "level": "error",
90
- "date": "15-03-2024 10:22:03",
91
- "module": "payments",
92
- "action": "charge",
93
- "stack": [
94
- "Error: Card declined",
95
- " at chargeCard (/app/src/payments.ts:42:11)"
96
- ]
97
- }
98
- ]
99
- }
100
- ```
101
-
102
- Differences from `FileLog`:
103
- - `name = "fileJson"` (**not** `"json"` — use this exact string for `log.channel("fileJson")`)
104
- - `extension` is always `"json"` — the option is silently ignored
105
- - Error `stack` is stored as `string[]` (split on newlines) easy to query in aggregators
106
- - `content` holds the original user-supplied `message` (not a pre-formatted line)
107
- - Corrupted existing file reinitialized to `{ messages: [] }` on next write (does not throw)
108
- - **Safe serialization by construction.** All writes go through `safe-stable-stringify` with a custom `Error` replacer — circular refs become `"[Circular]"`, BigInt is stringified, functions/symbols are dropped, nested `Error` instances expand to `{ name, message, stack, ...enumerable }`. A context payload with a class graph or circular reference will never throw during the write.
109
-
110
- ## Shared config — `BasicLogConfigurations`
111
-
112
- Every channel constructor accepts at minimum:
113
-
114
- ```ts
115
- type BasicLogConfigurations = {
116
- levels?: LogLevel[]; // whitelist omit or [] to allow all
117
- filter?: (data: LoggingData) => boolean; // custom predicate
118
- dateFormat?: { date?: string; time?: string }; // Day.js format strings
119
- context?: (data) => Promise<Record<string, any>>; // reserved — not yet read
120
- };
121
- ```
122
-
123
- Concrete file channels extend this with their storage/chunk/rotate/groupBy options via intersection.
124
-
125
- ## Picking a channel by name at runtime
126
-
127
- ```ts
128
- log.channel("console"); // → ConsoleLog | undefined
129
- log.channel("file"); // FileLog | undefined
130
- log.channel("fileJson"); // note the name NOT "json"
131
- ```
132
-
133
- If two channels share a `name`, only one is reachable this way the search returns the first match.
134
-
135
- ## See also
136
-
137
- - [`@warlock.js/logger/configure-logger/SKILL.md`](@warlock.js/logger/configure-logger/SKILL.md) registering channels at startup
138
- - [`@warlock.js/logger/filter-log-entries/SKILL.md`](@warlock.js/logger/filter-log-entries/SKILL.md) — `levels` and `filter` config in detail
139
- - [`@warlock.js/logger/write-custom-log-channel/SKILL.md`](@warlock.js/logger/write-custom-log-channel/SKILL.md) extending `LogChannel` for custom sinks
1
+ ---
2
+ name: pick-log-channel
3
+ description: '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 — `@warlock.js/logger/ship-logs-to-sentry/SKILL.md`; 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`.'
4
+ ---
5
+
6
+ # Channels — which one to pick and how to configure it
7
+
8
+ Four built-in channels (`SentryLog` needs the optional `@sentry/node` peer). A channel is a destination for a log entry — the logger fans out every entry to every registered channel in parallel.
9
+
10
+ ## The decision
11
+
12
+ | Need | Pick |
13
+ |---|---|
14
+ | Local dev, colored output in the terminal | `ConsoleLog` |
15
+ | Plain text `.log` files on disk — humans read them | `FileLog` |
16
+ | Structured `.json` files — a log aggregator (Loki / Datadog / Elastic) reads them | `JSONFileLog` |
17
+ | Errors & warnings into Sentry (events + breadcrumbs) | `SentryLog` |
18
+
19
+ Most production setups use **two** channels: `ConsoleLog` + one file channel. Dev uses `ConsoleLog` only.
20
+
21
+ ## `ConsoleLog`
22
+
23
+ Zero config. Colored, icon-prefixed lines to the terminal.
24
+
25
+ ```ts
26
+ import { ConsoleLog } from "@warlock.js/logger";
27
+
28
+ new ConsoleLog();
29
+ // (2024-03-15T10:22:00.000Z) [auth] [hashPassword] Hashing started
30
+ // (2024-03-15T10:22:01.482Z) [users] [register] New user created
31
+ // ✗ (2024-03-15T10:22:03.111Z) [payments] [charge] Card declined
32
+ ```
33
+
34
+ Properties:
35
+ - `name = "console"`, `terminal = true`
36
+ - Accepts `ConsoleLogConfig` `levels`, `filter`, `dateFormat`, `showContext`, `contextDepth`
37
+ - If `message` is an object, a second `console.log(message)` is issued so Node's inspector can expand it
38
+
39
+ ### Showing context
40
+
41
+ By default `ConsoleLog` drops the `context` payload (the file/JSON channels still keep it). Flip `showContext: true` to render it on a second line — useful in development:
42
+
43
+ ```ts
44
+ new ConsoleLog({ showContext: true });
45
+
46
+ log.info("payments", "charge", "card declined", { userId: 42, amount: 1999 });
47
+ // (…) [payments] [charge] card declined
48
+ // ↳ { userId: 42, amount: 1999 }
49
+ ```
50
+
51
+ Tune `contextDepth` (default `4`) to clamp how deep `util.inspect` recurses into nested objects.
52
+
53
+ ## `FileLog`
54
+
55
+ Plain text. Buffers in memory, flushes to disk periodically.
56
+
57
+ ```ts
58
+ import { FileLog } from "@warlock.js/logger";
59
+
60
+ new FileLog({
61
+ storagePath: "./storage/logs", // default: process.cwd() + "/storage/logs"
62
+ name: "app", // default: "app"
63
+ extension: "log", // default: "log"
64
+ chunk: "daily", // "single" (default) | "daily" | "hourly"
65
+ rotate: true, // default: true
66
+ maxFileSize: 10 * 1024 * 1024, // default: 10MBtriggers rotation
67
+ maxMessagesToWrite: 100, // default: 100 — flush threshold
68
+ groupBy: ["level", "module"], // optional subdirectory nesting
69
+ });
70
+ ```
71
+
72
+ Line format: `[date time] [level] [module][action]: message` — or a `[trace]` block when `message` is an `Error`.
73
+
74
+ ### Key gotchas
75
+
76
+ - **Buffers!** Messages sit in memory until either `maxMessagesToWrite` is reached, 5 seconds pass, or `flushSync()` is called. A process that crashes without flushing loses buffered entries.
77
+ - **`chunk: "daily"` picks a filename per day.** File name becomes `DD-MM-YYYY.log`. Combined with `rotate: true`, rotated archives get `Date.now()` suffixed.
78
+ - **`groupBy` nests directories.** `groupBy: ["level", "module"]` produces `storage/logs/error/payments/app.log`. Order matters.
79
+ - **Dispose channels you discard.** A live `FileLog` keeps a 5-second flush interval running. If you swap the channel list at runtime (reconfigure the logger), call `channel.dispose()` on the old instance — it clears that timer and drains the buffer one last time. Skipping it leaks one timer per discarded channel and keeps the event loop alive. (Channels that live for the whole process don't need this — process exit clears the timer.)
80
+
81
+ ## `JSONFileLog`
82
+
83
+ Subclass of `FileLog` — same buffering, chunking, rotation, grouping. Output is a JSON object with a `messages` array:
84
+
85
+ ```json
86
+ {
87
+ "messages": [
88
+ {
89
+ "content": "Card declined",
90
+ "level": "error",
91
+ "date": "15-03-2024 10:22:03",
92
+ "module": "payments",
93
+ "action": "charge",
94
+ "stack": [
95
+ "Error: Card declined",
96
+ " at chargeCard (/app/src/payments.ts:42:11)"
97
+ ]
98
+ }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ Differences from `FileLog`:
104
+ - `name = "fileJson"` (**not** `"json"` — use this exact string for `log.channel("fileJson")`)
105
+ - `extension` is always `"json"` — the option is silently ignored
106
+ - Error `stack` is stored as `string[]` (split on newlines) — easy to query in aggregators
107
+ - `content` holds the original user-supplied `message` (not a pre-formatted line)
108
+ - Corrupted existing file reinitialized to `{ messages: [] }` on next write (does not throw)
109
+ - **Safe serialization by construction.** All writes go through `safe-stable-stringify` with a custom `Error` replacer — circular refs become `"[Circular]"`, BigInt is stringified, functions/symbols are dropped, nested `Error` instances expand to `{ name, message, stack, ...enumerable }`. A context payload with a class graph or circular reference will never throw during the write.
110
+
111
+ ## `SentryLog`
112
+
113
+ Forwards entries to Sentry — `error` / `warn` become events (`captureException` for `Error` messages, `captureMessage` otherwise), every other level a breadcrumb (no quota). `@sentry/node` is an optional peer, lazily imported; pass an existing `client` or `options`.
114
+
115
+ ```ts
116
+ import * as Sentry from "@sentry/node";
117
+ import { SentryLog } from "@warlock.js/logger";
118
+
119
+ new SentryLog({ client: Sentry, eventLevels: ["error", "warn"] });
120
+ ```
121
+
122
+ Full guide — level mapping, init modes, shutdown draining: [`ship-logs-to-sentry`](@warlock.js/logger/ship-logs-to-sentry/SKILL.md).
123
+
124
+ ## Shared config — `BasicLogConfigurations`
125
+
126
+ Every channel constructor accepts at minimum:
127
+
128
+ ```ts
129
+ type BasicLogConfigurations = {
130
+ levels?: LogLevel[]; // whitelist omit or [] to allow all
131
+ filter?: (data: LoggingData) => boolean; // custom predicate
132
+ dateFormat?: { date?: string; time?: string }; // Day.js format strings
133
+ context?: (data) => Promise<Record<string, any>>; // reservednot yet read
134
+ };
135
+ ```
136
+
137
+ Concrete file channels extend this with their storage/chunk/rotate/groupBy options via intersection.
138
+
139
+ ## Picking a channel by name at runtime
140
+
141
+ ```ts
142
+ log.channel("console"); // → ConsoleLog | undefined
143
+ log.channel("file"); // → FileLog | undefined
144
+ log.channel("fileJson"); // ← note the name — NOT "json"
145
+ log.channel("sentry"); // → SentryLog | undefined
146
+ ```
147
+
148
+ If two channels share a `name`, only one is reachable this way — the search returns the first match.
149
+
150
+ ## See also
151
+
152
+ - [`@warlock.js/logger/configure-logger/SKILL.md`](@warlock.js/logger/configure-logger/SKILL.md) — registering channels at startup
153
+ - [`@warlock.js/logger/filter-log-entries/SKILL.md`](@warlock.js/logger/filter-log-entries/SKILL.md) — `levels` and `filter` config in detail
154
+ - [`@warlock.js/logger/ship-logs-to-sentry/SKILL.md`](@warlock.js/logger/ship-logs-to-sentry/SKILL.md) — the `SentryLog` channel in depth
155
+ - [`@warlock.js/logger/write-custom-log-channel/SKILL.md`](@warlock.js/logger/write-custom-log-channel/SKILL.md) — extending `LogChannel` for custom sinks
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: ship-logs-to-sentry
3
+ description: '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 — `@warlock.js/logger/pick-log-channel/SKILL.md`; custom sinks — `@warlock.js/logger/write-custom-log-channel/SKILL.md`; graceful-shutdown flushing — `@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`; Slack alerting recipe.'
4
+ ---
5
+
6
+ # Ship logs to Sentry — the `SentryLog` channel
7
+
8
+ `SentryLog` forwards log entries to Sentry. It's the one built-in channel that needs an external SDK, so `@sentry/node` is an **optional peer** — install it only if you use this channel:
9
+
10
+ ```bash
11
+ npm install @sentry/node
12
+ ```
13
+
14
+ ## Two ways to wire it
15
+
16
+ ### Reuse an existing Sentry client (existing apps)
17
+
18
+ If your app already calls `Sentry.init(...)`, pass the namespace as `client`. The channel forwards through it and never re-imports or re-initializes the SDK:
19
+
20
+ ```ts
21
+ import * as Sentry from "@sentry/node";
22
+ import { log, SentryLog } from "@warlock.js/logger";
23
+
24
+ Sentry.init({ dsn: process.env.SENTRY_DSN, environment: "production" });
25
+
26
+ log.addChannel(new SentryLog({ client: Sentry }));
27
+ ```
28
+
29
+ ### Let the channel initialize Sentry (new apps)
30
+
31
+ Pass `options`; the channel lazily imports `@sentry/node` and calls `Sentry.init` once — guarded so it never clobbers an existing client:
32
+
33
+ ```ts
34
+ import { log, SentryLog } from "@warlock.js/logger";
35
+
36
+ log.addChannel(
37
+ new SentryLog({
38
+ options: {
39
+ dsn: process.env.SENTRY_DSN,
40
+ environment: process.env.NODE_ENV,
41
+ release: process.env.GIT_SHA,
42
+ },
43
+ }),
44
+ );
45
+ ```
46
+
47
+ With neither `client` nor `options`, the channel reuses whatever global Sentry client the host already initialized.
48
+
49
+ ## How levels map to Sentry
50
+
51
+ This is the quota-control decision. Only `eventLevels` create Sentry **events** (which consume your error quota); every other level becomes a **breadcrumb** that rides along with the next event for free.
52
+
53
+ | Logger level | Default | Sentry call |
54
+ |---|---|---|
55
+ | `fatal` | event | `captureException` for an `Error` message, else `captureMessage(…, "fatal")` |
56
+ | `error` | event | `captureException` for an `Error` message, else `captureMessage(…, "error")` |
57
+ | `warn` | event | `captureMessage(…, "warning")` |
58
+ | `success` | breadcrumb | `addBreadcrumb({ level: "info" })` |
59
+ | `info` | breadcrumb | `addBreadcrumb({ level: "info" })` |
60
+ | `debug` | breadcrumb | `addBreadcrumb({ level: "debug" })` |
61
+
62
+ - **Errors keep their stack.** A `message` that is an `Error` goes through `captureException`, so Sentry parses the real stack and groups correctly — never pre-stringify the error.
63
+ - **`module` / `action` become tags** and the entry's `context` becomes a structured Sentry context, both scoped to that single event via `withScope`.
64
+ - **`success` has no Sentry severity** — it's reported as `info`.
65
+
66
+ ### Tuning what becomes an event
67
+
68
+ ```ts
69
+ // Only errors create events; warnings drop to breadcrumbs.
70
+ new SentryLog({ client: Sentry, eventLevels: ["error"] });
71
+
72
+ // Errors + warnings + an info stream as events (noisier, more quota).
73
+ new SentryLog({ client: Sentry, eventLevels: ["error", "warn", "info"] });
74
+ ```
75
+
76
+ `levels` and `filter` from `BasicLogConfigurations` apply first — a channel-level `levels: ["error", "warn"]` drops everything else before it reaches Sentry at all.
77
+
78
+ ## Draining on shutdown
79
+
80
+ Sentry sends events asynchronously over the network, so a synchronous flush can't wait on them. `SentryLog.flush()` calls `Sentry.flush(timeout)`; drain it on your graceful-shutdown path:
81
+
82
+ ```ts
83
+ async function shutdown() {
84
+ await httpServer.close();
85
+ await log.flush(); // SentryLog.flush() → Sentry.flush(flushTimeout)
86
+ process.exit(0);
87
+ }
88
+
89
+ process.once("SIGTERM", shutdown);
90
+ ```
91
+
92
+ `flushTimeout` (default `2000` ms) bounds the wait so an unreachable Sentry can't hang shutdown. `autoFlushOn` uses the **synchronous** `flushSync()`, which does *not* drain Sentry — wire `await log.flush()` yourself. See [`flush-logs-on-shutdown`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md).
93
+
94
+ ## If `@sentry/node` isn't installed
95
+
96
+ The channel never crashes your app: the dynamic import failure is swallowed, the install instructions are written to stderr **once**, and entries are dropped silently thereafter. So registering `SentryLog` in shared config is safe even in an environment where Sentry isn't installed.
97
+
98
+ ## Config reference
99
+
100
+ | Option | Type | Default | Description |
101
+ |---|---|---|---|
102
+ | `client` | Sentry namespace / forwarder | — | Reuse an already-initialized Sentry instance. |
103
+ | `options` | `SentryInitOptions` | — | Sentry init options (mirrors `@sentry/node`'s `NodeOptions`) — used when the channel owns Sentry. |
104
+ | `eventLevels` | `LogLevel[]` | `["fatal", "error", "warn"]` | Levels sent as events; the rest become breadcrumbs. |
105
+ | `flushTimeout` | `number` | `2000` | Ms `flush()` waits for the transport to drain. |
106
+ | `levels`, `filter`, `dateFormat`, `redact` | — | — | Inherited from `BasicLogConfigurations`. |
107
+
108
+ ## Don't do
109
+
110
+ - **Don't double-init Sentry.** Pass *either* `client` *or* `options`, not both. The channel guards against double-init, but a single owner of `Sentry.init` is cleaner.
111
+ - **Don't send every level as an event.** `eventLevels` with `info`/`debug`/`success` will flood your Sentry quota — keep them as breadcrumbs.
112
+ - **Don't rely on `autoFlushOn` for Sentry.** It's synchronous; a network channel needs `await log.flush()`.
113
+
114
+ ## See also
115
+
116
+ - [`@warlock.js/logger/flush-logs-on-shutdown/SKILL.md`](@warlock.js/logger/flush-logs-on-shutdown/SKILL.md) — `await log.flush()` on shutdown
117
+ - [`@warlock.js/logger/pick-log-channel/SKILL.md`](@warlock.js/logger/pick-log-channel/SKILL.md) — the console / file channels
118
+ - [`@warlock.js/logger/write-custom-log-channel/SKILL.md`](@warlock.js/logger/write-custom-log-channel/SKILL.md) — build your own sink