@rawnodes/logger 2.7.1 → 2.7.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/README.md +55 -1
- package/dist/index.d.mts +143 -7
- package/dist/index.d.ts +143 -7
- package/dist/index.js +257 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +257 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,6 +134,19 @@ const logger = Logger.create({
|
|
|
134
134
|
|
|
135
135
|
This is useful for sending only critical errors to alerting systems while keeping verbose logs in console.
|
|
136
136
|
|
|
137
|
+
#### Level precedence
|
|
138
|
+
|
|
139
|
+
When deciding whether to emit a log entry, the following order applies — **most specific wins**:
|
|
140
|
+
|
|
141
|
+
1. **Global override** from `setLevelOverride(...)` or the top-level `level.rules` array
|
|
142
|
+
2. **Per-transport rule** (a matching entry in `console.rules` / `file.rules` / etc.)
|
|
143
|
+
3. **Per-transport level** (`console.level`, `file.level`, …)
|
|
144
|
+
4. **Global default level** (`level` or `level.default`)
|
|
145
|
+
|
|
146
|
+
Practically: a matching `setLevelOverride({ userId: 123 }, 'debug')` will surface debug logs to **every** transport, even one whose own `level` is set to `info`. This is intentional — dynamic overrides are for targeted troubleshooting, and they wouldn't be useful if a downstream transport could silently swallow the messages they just enabled.
|
|
147
|
+
|
|
148
|
+
If you need a transport to stay narrow while overrides are active, use `rules` on that transport (level 2), not the plain `level` field.
|
|
149
|
+
|
|
137
150
|
### Per-Transport Rules
|
|
138
151
|
|
|
139
152
|
Each transport can have its own filtering rules. Use `level: 'off'` with rules to create a whitelist pattern:
|
|
@@ -635,6 +648,43 @@ const masker = createMasker({
|
|
|
635
648
|
|
|
636
649
|
**Default masked patterns:** `password`, `secret`, `token`, `apikey`, `api_key`, `api-key`, `auth`, `credential`, `private`
|
|
637
650
|
|
|
651
|
+
## Error logging
|
|
652
|
+
|
|
653
|
+
`error()` accepts an error value as the **first** argument. This keeps call sites short in the common case — you forward whatever `catch` produced without pre-normalizing it.
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
try {
|
|
657
|
+
await doWork();
|
|
658
|
+
} catch (err) {
|
|
659
|
+
// err: unknown — passes straight through, no cast needed
|
|
660
|
+
logger.error(err, 'doWork failed', { userId });
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
Supported shapes:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
logger.error('something broke'); // message only
|
|
668
|
+
logger.error('something broke', { retries: 3 }); // message + meta
|
|
669
|
+
logger.error(err); // error only (uses err.message)
|
|
670
|
+
logger.error(err, 'doWork failed'); // error + message
|
|
671
|
+
logger.error(err, { userId }); // error + meta
|
|
672
|
+
logger.error(err, 'doWork failed', { userId }); // error + message + meta
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
The first argument is interpreted as an error whenever it is **not a string**. That includes anything TypeScript's `catch` clause can produce (`Error`, plain objects, primitives, `null`, `undefined`). Non-`Error` values are normalized via `serializeError`:
|
|
676
|
+
|
|
677
|
+
| Input | Log fields extracted |
|
|
678
|
+
|---------------------------------------|-------------------------------------------------------|
|
|
679
|
+
| `new Error('boom')` | `errorMessage`, `stack`, `errorName` (if not `Error`) |
|
|
680
|
+
| `'string value'` | `errorMessage: 'string value'` |
|
|
681
|
+
| axios error | `errorMessage`, `stack`, `http: { status, url, … }` |
|
|
682
|
+
| fetch-style `{ response, config }` | Same, extracted heuristically |
|
|
683
|
+
| `{ message: 'x', code: 'ECONN' }` | `errorMessage`, `errorCode` |
|
|
684
|
+
| `null` / `undefined` | `errorMessage: 'Unknown error'` |
|
|
685
|
+
|
|
686
|
+
String-as-first-arg is always a message, never an error. If you want to log a string *as* an error, wrap it: `logger.error(new Error(str))`.
|
|
687
|
+
|
|
638
688
|
## Logfmt Utilities
|
|
639
689
|
|
|
640
690
|
Helper functions for logfmt format:
|
|
@@ -661,7 +711,11 @@ class Logger<TContext> {
|
|
|
661
711
|
getStore(): LoggerStore<TContext>;
|
|
662
712
|
|
|
663
713
|
// Logging
|
|
664
|
-
error(
|
|
714
|
+
// error() accepts an error value as the first argument — including `unknown`
|
|
715
|
+
// (what `catch (err)` produces under TypeScript's strict mode). Non-Error
|
|
716
|
+
// values are normalized via `serializeError` so logs still get a sensible
|
|
717
|
+
// `errorMessage` / `stack` / HTTP payload. See "Error logging" below.
|
|
718
|
+
error(errorOrMessage: Error | string | unknown, messageOrMeta?: string | Meta, meta?: Meta): void;
|
|
665
719
|
warn(message: string, meta?: Meta): void;
|
|
666
720
|
info(message: string, meta?: Meta): void;
|
|
667
721
|
http(message: string, meta?: Meta): void;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
|
|
4
3
|
declare const LOG_LEVELS: {
|
|
@@ -45,6 +44,47 @@ interface HttpTransportBaseConfig {
|
|
|
45
44
|
flushInterval?: number;
|
|
46
45
|
maxRetries?: number;
|
|
47
46
|
retryDelay?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Maximum number of messages the in-memory queue will hold before dropping.
|
|
49
|
+
* Provides a hard upper bound on memory usage when a transport is degraded
|
|
50
|
+
* or offline — without this, a failing transport + steady log volume grows
|
|
51
|
+
* the queue until OOM. Default: unbounded.
|
|
52
|
+
*
|
|
53
|
+
* Recommended for production: `10_000` — enough to absorb transient blips,
|
|
54
|
+
* small enough to avoid runaway memory.
|
|
55
|
+
*/
|
|
56
|
+
maxQueueSize?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Policy applied when the queue is full.
|
|
59
|
+
* - `'drop-oldest'` (default): prefers keeping recent events — during an
|
|
60
|
+
* outage old logs are usually stale.
|
|
61
|
+
* - `'drop-newest'`: prefers preserving historical context — useful if you
|
|
62
|
+
* care more about the events leading up to the outage than the noise
|
|
63
|
+
* happening during it.
|
|
64
|
+
*/
|
|
65
|
+
dropPolicy?: 'drop-oldest' | 'drop-newest';
|
|
66
|
+
/**
|
|
67
|
+
* Called when messages are dropped due to queue overflow. Receives only the
|
|
68
|
+
* messages that were discarded. If not provided, drops are silent.
|
|
69
|
+
*
|
|
70
|
+
* Callback MUST NOT throw; if it does, the exception is swallowed.
|
|
71
|
+
*/
|
|
72
|
+
onDrop?: (droppedMessages: unknown[]) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Called when a batch fails after all retries and messages are dropped.
|
|
75
|
+
* If not provided, the error is logged to stderr.
|
|
76
|
+
*
|
|
77
|
+
* The callback MUST NOT throw; if it does, the failure is swallowed and a
|
|
78
|
+
* stderr fallback is used. This is intentional — a logger must never
|
|
79
|
+
* propagate its own transport errors back into the host application.
|
|
80
|
+
*/
|
|
81
|
+
onError?: (error: Error, droppedMessages: unknown[]) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Timeout in milliseconds for a single outbound HTTP request. Default:
|
|
84
|
+
* `10_000`. Applies to Discord/Telegram `fetch`. CloudWatch uses the AWS
|
|
85
|
+
* SDK's own timeout configuration.
|
|
86
|
+
*/
|
|
87
|
+
requestTimeout?: number;
|
|
48
88
|
}
|
|
49
89
|
interface DiscordConfig extends HttpTransportBaseConfig {
|
|
50
90
|
webhookUrl: string;
|
|
@@ -73,6 +113,20 @@ interface LogStreamTemplateConfig {
|
|
|
73
113
|
template: string;
|
|
74
114
|
}
|
|
75
115
|
type LogStreamName = string | LogStreamPatternConfig | LogStreamTemplateConfig;
|
|
116
|
+
interface RelayConfig {
|
|
117
|
+
/** URL of the relay API (e.g., https://relay.example.com) */
|
|
118
|
+
apiUrl: string;
|
|
119
|
+
/** Authentication token for the relay server */
|
|
120
|
+
token: string;
|
|
121
|
+
/** Polling interval in ms (default: 30000) */
|
|
122
|
+
pollInterval?: number;
|
|
123
|
+
/** Ring buffer capacity - max logs held during reconnect (default: 1000) */
|
|
124
|
+
bufferSize?: number;
|
|
125
|
+
/** WebSocket reconnect base delay in ms (default: 1000) */
|
|
126
|
+
reconnectDelay?: number;
|
|
127
|
+
/** Max reconnect delay in ms (default: 30000) */
|
|
128
|
+
maxReconnectDelay?: number;
|
|
129
|
+
}
|
|
76
130
|
interface CloudWatchConfig extends HttpTransportBaseConfig {
|
|
77
131
|
logGroupName: string;
|
|
78
132
|
/** Log stream name - string, pattern config, or template config. Defaults to hostname pattern */
|
|
@@ -113,6 +167,8 @@ interface LoggerConfig {
|
|
|
113
167
|
discord?: DiscordConfig | DiscordConfig[];
|
|
114
168
|
telegram?: TelegramConfig | TelegramConfig[];
|
|
115
169
|
cloudwatch?: CloudWatchConfig | CloudWatchConfig[];
|
|
170
|
+
/** Relay transport config — streams logs to a remote server via WebSocket (runs in worker thread) */
|
|
171
|
+
relay?: RelayConfig;
|
|
116
172
|
/** Enable caller info (file:line) in logs. Pass true for defaults or CallerConfig for options */
|
|
117
173
|
caller?: boolean | CallerConfig;
|
|
118
174
|
/** Hostname to include in all log entries. Defaults to os.hostname() if not specified */
|
|
@@ -140,7 +196,7 @@ declare class LoggerStore<TContext extends LoggerContext = LoggerContext> {
|
|
|
140
196
|
declare class Logger<TContext extends LoggerContext = LoggerContext> {
|
|
141
197
|
private state;
|
|
142
198
|
private context;
|
|
143
|
-
private profileTimers
|
|
199
|
+
private profileTimers?;
|
|
144
200
|
private constructor();
|
|
145
201
|
static create<TContext extends LoggerContext = LoggerContext>(config: LoggerConfig, store?: LoggerStore<TContext>): Logger<TContext>;
|
|
146
202
|
for(context: string): Logger<TContext>;
|
|
@@ -152,10 +208,29 @@ declare class Logger<TContext extends LoggerContext = LoggerContext> {
|
|
|
152
208
|
/**
|
|
153
209
|
* Gracefully shutdown the logger, flushing all pending messages.
|
|
154
210
|
* Should be called before process exit to ensure no logs are lost.
|
|
211
|
+
*
|
|
212
|
+
* `timeoutMs` is forwarded to each transport's `close()` as a per-transport
|
|
213
|
+
* cap; it prevents a dead transport from blocking the whole shutdown.
|
|
214
|
+
* Default: 5000ms. Pass `Infinity` for legacy unbounded behaviour.
|
|
155
215
|
*/
|
|
156
|
-
shutdown(): Promise<void>;
|
|
216
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
157
217
|
profile(id: string, meta?: object): void;
|
|
158
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Log an error. Supported shapes:
|
|
220
|
+
* error(message: string)
|
|
221
|
+
* error(message: string, meta)
|
|
222
|
+
* error(error: Error | unknown)
|
|
223
|
+
* error(error: Error | unknown, message: string)
|
|
224
|
+
* error(error: Error | unknown, meta)
|
|
225
|
+
* error(error: Error | unknown, message: string, meta)
|
|
226
|
+
*
|
|
227
|
+
* The first argument is treated as an error value whenever it is not a string.
|
|
228
|
+
* Non-Error values (plain objects, numbers, etc. — e.g. anything caught by
|
|
229
|
+
* TypeScript's `catch (err)` clause, which is typed as `unknown`) are passed
|
|
230
|
+
* through `serializeError` so they produce a sensible `errorMessage` and, when
|
|
231
|
+
* possible, a `stack` / HTTP diagnostic payload.
|
|
232
|
+
*/
|
|
233
|
+
error(errorOrMessage: Error | string | unknown, messageOrMeta?: string | Meta, meta?: Meta): void;
|
|
159
234
|
warn(message: string, meta?: Meta): void;
|
|
160
235
|
info(message: string, meta?: Meta): void;
|
|
161
236
|
http(message: string, meta?: Meta): void;
|
|
@@ -185,6 +260,7 @@ interface BufferedMessage {
|
|
|
185
260
|
context?: string;
|
|
186
261
|
meta?: Record<string, unknown>;
|
|
187
262
|
}
|
|
263
|
+
type DropPolicy = 'drop-oldest' | 'drop-newest';
|
|
188
264
|
interface BufferOptions {
|
|
189
265
|
batchSize: number;
|
|
190
266
|
flushInterval: number;
|
|
@@ -192,6 +268,25 @@ interface BufferOptions {
|
|
|
192
268
|
retryDelay: number;
|
|
193
269
|
onFlush: (messages: BufferedMessage[]) => Promise<void>;
|
|
194
270
|
onError?: (error: Error, messages: BufferedMessage[]) => void;
|
|
271
|
+
/**
|
|
272
|
+
* Maximum number of messages the queue will hold before dropping. When the
|
|
273
|
+
* queue is full, behaviour follows `dropPolicy`. Default: unbounded.
|
|
274
|
+
*/
|
|
275
|
+
maxQueueSize?: number;
|
|
276
|
+
/**
|
|
277
|
+
* Policy applied when the queue is at `maxQueueSize` and a new message
|
|
278
|
+
* arrives. Default: `'drop-oldest'` — prefers keeping recent events, since
|
|
279
|
+
* during an outage the old ones are usually stale anyway.
|
|
280
|
+
*/
|
|
281
|
+
dropPolicy?: DropPolicy;
|
|
282
|
+
/**
|
|
283
|
+
* Called when messages are dropped due to queue overflow. Receives the
|
|
284
|
+
* messages that were discarded (not the remaining queue). If not provided,
|
|
285
|
+
* the drop is silent — consumers can still track it via buffer metrics.
|
|
286
|
+
*
|
|
287
|
+
* Callback MUST NOT throw; if it does, the exception is swallowed.
|
|
288
|
+
*/
|
|
289
|
+
onDrop?: (droppedMessages: BufferedMessage[]) => void;
|
|
195
290
|
}
|
|
196
291
|
declare class MessageBuffer {
|
|
197
292
|
private options;
|
|
@@ -199,10 +294,24 @@ declare class MessageBuffer {
|
|
|
199
294
|
private timer;
|
|
200
295
|
private flushing;
|
|
201
296
|
private closed;
|
|
297
|
+
private droppedCount;
|
|
202
298
|
constructor(options: BufferOptions);
|
|
203
299
|
add(message: BufferedMessage): void;
|
|
204
300
|
flush(): Promise<void>;
|
|
205
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Stops accepting new messages and attempts to flush what's already buffered.
|
|
303
|
+
* Returns when the queue is drained OR when `timeoutMs` elapses. A timeout
|
|
304
|
+
* is essential during graceful shutdown — without it, a dead transport would
|
|
305
|
+
* block process exit indefinitely, and orchestrators like Kubernetes would
|
|
306
|
+
* escalate to SIGKILL after their grace period.
|
|
307
|
+
*
|
|
308
|
+
* Default timeout: 5 seconds. Pass `Infinity` to preserve legacy behaviour.
|
|
309
|
+
*/
|
|
310
|
+
close(timeoutMs?: number): Promise<void>;
|
|
311
|
+
/** Current number of messages waiting in the queue. */
|
|
312
|
+
get size(): number;
|
|
313
|
+
/** Total number of messages dropped due to queue overflow since creation. */
|
|
314
|
+
get droppedTotal(): number;
|
|
206
315
|
private scheduleFlush;
|
|
207
316
|
private clearTimer;
|
|
208
317
|
private sendWithRetry;
|
|
@@ -214,12 +323,35 @@ interface BaseHttpTransportOptions {
|
|
|
214
323
|
flushInterval?: number;
|
|
215
324
|
maxRetries?: number;
|
|
216
325
|
retryDelay?: number;
|
|
326
|
+
maxQueueSize?: number;
|
|
327
|
+
dropPolicy?: DropPolicy;
|
|
328
|
+
onDrop?: (droppedMessages: BufferedMessage[]) => void;
|
|
329
|
+
/**
|
|
330
|
+
* Called when a batch fails after all retries and messages are dropped.
|
|
331
|
+
* If not provided, the error is logged to stderr.
|
|
332
|
+
*
|
|
333
|
+
* The callback MUST NOT throw; if it does, the failure is swallowed and a
|
|
334
|
+
* stderr fallback is used. A logger must never propagate transport errors
|
|
335
|
+
* back into the host application.
|
|
336
|
+
*/
|
|
337
|
+
onError?: (error: Error, droppedMessages: BufferedMessage[]) => void;
|
|
217
338
|
}
|
|
218
|
-
declare abstract class BaseHttpTransport
|
|
339
|
+
declare abstract class BaseHttpTransport {
|
|
219
340
|
protected buffer: MessageBuffer;
|
|
341
|
+
private onErrorCallback?;
|
|
220
342
|
constructor(opts?: BaseHttpTransportOptions);
|
|
221
343
|
log(info: Record<string, unknown>, callback: () => void): void;
|
|
222
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Stop accepting new messages and flush what's buffered. `timeoutMs` caps
|
|
346
|
+
* how long the flush may take; after that the remaining queue is discarded
|
|
347
|
+
* so shutdown can complete. Default 5000ms.
|
|
348
|
+
*/
|
|
349
|
+
close(timeoutMs?: number): Promise<void>;
|
|
350
|
+
/** Current buffered message count and total drops since creation. */
|
|
351
|
+
getMetrics(): {
|
|
352
|
+
queueSize: number;
|
|
353
|
+
droppedTotal: number;
|
|
354
|
+
};
|
|
223
355
|
protected transformMessage(info: Record<string, unknown>): BufferedMessage;
|
|
224
356
|
protected handleError(error: Error, messages: BufferedMessage[]): void;
|
|
225
357
|
protected abstract sendBatch(messages: BufferedMessage[]): Promise<void>;
|
|
@@ -231,6 +363,7 @@ declare function matchesContext(storeContext: LoggerContext | undefined, loggerC
|
|
|
231
363
|
|
|
232
364
|
declare class DiscordTransport extends BaseHttpTransport {
|
|
233
365
|
private config;
|
|
366
|
+
private requestTimeout;
|
|
234
367
|
constructor(config: DiscordConfig);
|
|
235
368
|
protected sendBatch(messages: BufferedMessage[]): Promise<void>;
|
|
236
369
|
private sendEmbedBatch;
|
|
@@ -246,6 +379,7 @@ declare class DiscordTransport extends BaseHttpTransport {
|
|
|
246
379
|
declare class TelegramTransport extends BaseHttpTransport {
|
|
247
380
|
private config;
|
|
248
381
|
private apiUrl;
|
|
382
|
+
private requestTimeout;
|
|
249
383
|
constructor(config: TelegramConfig);
|
|
250
384
|
protected sendBatch(messages: BufferedMessage[]): Promise<void>;
|
|
251
385
|
private formatBatchMessage;
|
|
@@ -412,6 +546,7 @@ declare const LoggerConfigSchema: z.ZodObject<{
|
|
|
412
546
|
discord: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<DiscordConfig, unknown, z.core.$ZodTypeInternals<DiscordConfig, unknown>>, z.ZodArray<z.ZodType<DiscordConfig, unknown, z.core.$ZodTypeInternals<DiscordConfig, unknown>>>]>>;
|
|
413
547
|
telegram: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<TelegramConfig, unknown, z.core.$ZodTypeInternals<TelegramConfig, unknown>>, z.ZodArray<z.ZodType<TelegramConfig, unknown, z.core.$ZodTypeInternals<TelegramConfig, unknown>>>]>>;
|
|
414
548
|
cloudwatch: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>, z.ZodArray<z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>>]>>;
|
|
549
|
+
relay: z.ZodOptional<z.ZodType<RelayConfig, unknown, z.core.$ZodTypeInternals<RelayConfig, unknown>>>;
|
|
415
550
|
caller: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<CallerConfig, unknown, z.core.$ZodTypeInternals<CallerConfig, unknown>>]>>;
|
|
416
551
|
hostname: z.ZodOptional<z.ZodString>;
|
|
417
552
|
autoShutdown: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<AutoShutdownConfig, unknown, z.core.$ZodTypeInternals<AutoShutdownConfig, unknown>>]>>;
|
|
@@ -424,6 +559,7 @@ declare function safeValidateConfig(config: unknown): z.ZodSafeParseResult<{
|
|
|
424
559
|
discord?: DiscordConfig | DiscordConfig[] | undefined;
|
|
425
560
|
telegram?: TelegramConfig | TelegramConfig[] | undefined;
|
|
426
561
|
cloudwatch?: CloudWatchConfig | CloudWatchConfig[] | undefined;
|
|
562
|
+
relay?: RelayConfig | undefined;
|
|
427
563
|
caller?: boolean | CallerConfig | undefined;
|
|
428
564
|
hostname?: string | undefined;
|
|
429
565
|
autoShutdown?: boolean | AutoShutdownConfig | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
|
|
4
3
|
declare const LOG_LEVELS: {
|
|
@@ -45,6 +44,47 @@ interface HttpTransportBaseConfig {
|
|
|
45
44
|
flushInterval?: number;
|
|
46
45
|
maxRetries?: number;
|
|
47
46
|
retryDelay?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Maximum number of messages the in-memory queue will hold before dropping.
|
|
49
|
+
* Provides a hard upper bound on memory usage when a transport is degraded
|
|
50
|
+
* or offline — without this, a failing transport + steady log volume grows
|
|
51
|
+
* the queue until OOM. Default: unbounded.
|
|
52
|
+
*
|
|
53
|
+
* Recommended for production: `10_000` — enough to absorb transient blips,
|
|
54
|
+
* small enough to avoid runaway memory.
|
|
55
|
+
*/
|
|
56
|
+
maxQueueSize?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Policy applied when the queue is full.
|
|
59
|
+
* - `'drop-oldest'` (default): prefers keeping recent events — during an
|
|
60
|
+
* outage old logs are usually stale.
|
|
61
|
+
* - `'drop-newest'`: prefers preserving historical context — useful if you
|
|
62
|
+
* care more about the events leading up to the outage than the noise
|
|
63
|
+
* happening during it.
|
|
64
|
+
*/
|
|
65
|
+
dropPolicy?: 'drop-oldest' | 'drop-newest';
|
|
66
|
+
/**
|
|
67
|
+
* Called when messages are dropped due to queue overflow. Receives only the
|
|
68
|
+
* messages that were discarded. If not provided, drops are silent.
|
|
69
|
+
*
|
|
70
|
+
* Callback MUST NOT throw; if it does, the exception is swallowed.
|
|
71
|
+
*/
|
|
72
|
+
onDrop?: (droppedMessages: unknown[]) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Called when a batch fails after all retries and messages are dropped.
|
|
75
|
+
* If not provided, the error is logged to stderr.
|
|
76
|
+
*
|
|
77
|
+
* The callback MUST NOT throw; if it does, the failure is swallowed and a
|
|
78
|
+
* stderr fallback is used. This is intentional — a logger must never
|
|
79
|
+
* propagate its own transport errors back into the host application.
|
|
80
|
+
*/
|
|
81
|
+
onError?: (error: Error, droppedMessages: unknown[]) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Timeout in milliseconds for a single outbound HTTP request. Default:
|
|
84
|
+
* `10_000`. Applies to Discord/Telegram `fetch`. CloudWatch uses the AWS
|
|
85
|
+
* SDK's own timeout configuration.
|
|
86
|
+
*/
|
|
87
|
+
requestTimeout?: number;
|
|
48
88
|
}
|
|
49
89
|
interface DiscordConfig extends HttpTransportBaseConfig {
|
|
50
90
|
webhookUrl: string;
|
|
@@ -73,6 +113,20 @@ interface LogStreamTemplateConfig {
|
|
|
73
113
|
template: string;
|
|
74
114
|
}
|
|
75
115
|
type LogStreamName = string | LogStreamPatternConfig | LogStreamTemplateConfig;
|
|
116
|
+
interface RelayConfig {
|
|
117
|
+
/** URL of the relay API (e.g., https://relay.example.com) */
|
|
118
|
+
apiUrl: string;
|
|
119
|
+
/** Authentication token for the relay server */
|
|
120
|
+
token: string;
|
|
121
|
+
/** Polling interval in ms (default: 30000) */
|
|
122
|
+
pollInterval?: number;
|
|
123
|
+
/** Ring buffer capacity - max logs held during reconnect (default: 1000) */
|
|
124
|
+
bufferSize?: number;
|
|
125
|
+
/** WebSocket reconnect base delay in ms (default: 1000) */
|
|
126
|
+
reconnectDelay?: number;
|
|
127
|
+
/** Max reconnect delay in ms (default: 30000) */
|
|
128
|
+
maxReconnectDelay?: number;
|
|
129
|
+
}
|
|
76
130
|
interface CloudWatchConfig extends HttpTransportBaseConfig {
|
|
77
131
|
logGroupName: string;
|
|
78
132
|
/** Log stream name - string, pattern config, or template config. Defaults to hostname pattern */
|
|
@@ -113,6 +167,8 @@ interface LoggerConfig {
|
|
|
113
167
|
discord?: DiscordConfig | DiscordConfig[];
|
|
114
168
|
telegram?: TelegramConfig | TelegramConfig[];
|
|
115
169
|
cloudwatch?: CloudWatchConfig | CloudWatchConfig[];
|
|
170
|
+
/** Relay transport config — streams logs to a remote server via WebSocket (runs in worker thread) */
|
|
171
|
+
relay?: RelayConfig;
|
|
116
172
|
/** Enable caller info (file:line) in logs. Pass true for defaults or CallerConfig for options */
|
|
117
173
|
caller?: boolean | CallerConfig;
|
|
118
174
|
/** Hostname to include in all log entries. Defaults to os.hostname() if not specified */
|
|
@@ -140,7 +196,7 @@ declare class LoggerStore<TContext extends LoggerContext = LoggerContext> {
|
|
|
140
196
|
declare class Logger<TContext extends LoggerContext = LoggerContext> {
|
|
141
197
|
private state;
|
|
142
198
|
private context;
|
|
143
|
-
private profileTimers
|
|
199
|
+
private profileTimers?;
|
|
144
200
|
private constructor();
|
|
145
201
|
static create<TContext extends LoggerContext = LoggerContext>(config: LoggerConfig, store?: LoggerStore<TContext>): Logger<TContext>;
|
|
146
202
|
for(context: string): Logger<TContext>;
|
|
@@ -152,10 +208,29 @@ declare class Logger<TContext extends LoggerContext = LoggerContext> {
|
|
|
152
208
|
/**
|
|
153
209
|
* Gracefully shutdown the logger, flushing all pending messages.
|
|
154
210
|
* Should be called before process exit to ensure no logs are lost.
|
|
211
|
+
*
|
|
212
|
+
* `timeoutMs` is forwarded to each transport's `close()` as a per-transport
|
|
213
|
+
* cap; it prevents a dead transport from blocking the whole shutdown.
|
|
214
|
+
* Default: 5000ms. Pass `Infinity` for legacy unbounded behaviour.
|
|
155
215
|
*/
|
|
156
|
-
shutdown(): Promise<void>;
|
|
216
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
157
217
|
profile(id: string, meta?: object): void;
|
|
158
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Log an error. Supported shapes:
|
|
220
|
+
* error(message: string)
|
|
221
|
+
* error(message: string, meta)
|
|
222
|
+
* error(error: Error | unknown)
|
|
223
|
+
* error(error: Error | unknown, message: string)
|
|
224
|
+
* error(error: Error | unknown, meta)
|
|
225
|
+
* error(error: Error | unknown, message: string, meta)
|
|
226
|
+
*
|
|
227
|
+
* The first argument is treated as an error value whenever it is not a string.
|
|
228
|
+
* Non-Error values (plain objects, numbers, etc. — e.g. anything caught by
|
|
229
|
+
* TypeScript's `catch (err)` clause, which is typed as `unknown`) are passed
|
|
230
|
+
* through `serializeError` so they produce a sensible `errorMessage` and, when
|
|
231
|
+
* possible, a `stack` / HTTP diagnostic payload.
|
|
232
|
+
*/
|
|
233
|
+
error(errorOrMessage: Error | string | unknown, messageOrMeta?: string | Meta, meta?: Meta): void;
|
|
159
234
|
warn(message: string, meta?: Meta): void;
|
|
160
235
|
info(message: string, meta?: Meta): void;
|
|
161
236
|
http(message: string, meta?: Meta): void;
|
|
@@ -185,6 +260,7 @@ interface BufferedMessage {
|
|
|
185
260
|
context?: string;
|
|
186
261
|
meta?: Record<string, unknown>;
|
|
187
262
|
}
|
|
263
|
+
type DropPolicy = 'drop-oldest' | 'drop-newest';
|
|
188
264
|
interface BufferOptions {
|
|
189
265
|
batchSize: number;
|
|
190
266
|
flushInterval: number;
|
|
@@ -192,6 +268,25 @@ interface BufferOptions {
|
|
|
192
268
|
retryDelay: number;
|
|
193
269
|
onFlush: (messages: BufferedMessage[]) => Promise<void>;
|
|
194
270
|
onError?: (error: Error, messages: BufferedMessage[]) => void;
|
|
271
|
+
/**
|
|
272
|
+
* Maximum number of messages the queue will hold before dropping. When the
|
|
273
|
+
* queue is full, behaviour follows `dropPolicy`. Default: unbounded.
|
|
274
|
+
*/
|
|
275
|
+
maxQueueSize?: number;
|
|
276
|
+
/**
|
|
277
|
+
* Policy applied when the queue is at `maxQueueSize` and a new message
|
|
278
|
+
* arrives. Default: `'drop-oldest'` — prefers keeping recent events, since
|
|
279
|
+
* during an outage the old ones are usually stale anyway.
|
|
280
|
+
*/
|
|
281
|
+
dropPolicy?: DropPolicy;
|
|
282
|
+
/**
|
|
283
|
+
* Called when messages are dropped due to queue overflow. Receives the
|
|
284
|
+
* messages that were discarded (not the remaining queue). If not provided,
|
|
285
|
+
* the drop is silent — consumers can still track it via buffer metrics.
|
|
286
|
+
*
|
|
287
|
+
* Callback MUST NOT throw; if it does, the exception is swallowed.
|
|
288
|
+
*/
|
|
289
|
+
onDrop?: (droppedMessages: BufferedMessage[]) => void;
|
|
195
290
|
}
|
|
196
291
|
declare class MessageBuffer {
|
|
197
292
|
private options;
|
|
@@ -199,10 +294,24 @@ declare class MessageBuffer {
|
|
|
199
294
|
private timer;
|
|
200
295
|
private flushing;
|
|
201
296
|
private closed;
|
|
297
|
+
private droppedCount;
|
|
202
298
|
constructor(options: BufferOptions);
|
|
203
299
|
add(message: BufferedMessage): void;
|
|
204
300
|
flush(): Promise<void>;
|
|
205
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Stops accepting new messages and attempts to flush what's already buffered.
|
|
303
|
+
* Returns when the queue is drained OR when `timeoutMs` elapses. A timeout
|
|
304
|
+
* is essential during graceful shutdown — without it, a dead transport would
|
|
305
|
+
* block process exit indefinitely, and orchestrators like Kubernetes would
|
|
306
|
+
* escalate to SIGKILL after their grace period.
|
|
307
|
+
*
|
|
308
|
+
* Default timeout: 5 seconds. Pass `Infinity` to preserve legacy behaviour.
|
|
309
|
+
*/
|
|
310
|
+
close(timeoutMs?: number): Promise<void>;
|
|
311
|
+
/** Current number of messages waiting in the queue. */
|
|
312
|
+
get size(): number;
|
|
313
|
+
/** Total number of messages dropped due to queue overflow since creation. */
|
|
314
|
+
get droppedTotal(): number;
|
|
206
315
|
private scheduleFlush;
|
|
207
316
|
private clearTimer;
|
|
208
317
|
private sendWithRetry;
|
|
@@ -214,12 +323,35 @@ interface BaseHttpTransportOptions {
|
|
|
214
323
|
flushInterval?: number;
|
|
215
324
|
maxRetries?: number;
|
|
216
325
|
retryDelay?: number;
|
|
326
|
+
maxQueueSize?: number;
|
|
327
|
+
dropPolicy?: DropPolicy;
|
|
328
|
+
onDrop?: (droppedMessages: BufferedMessage[]) => void;
|
|
329
|
+
/**
|
|
330
|
+
* Called when a batch fails after all retries and messages are dropped.
|
|
331
|
+
* If not provided, the error is logged to stderr.
|
|
332
|
+
*
|
|
333
|
+
* The callback MUST NOT throw; if it does, the failure is swallowed and a
|
|
334
|
+
* stderr fallback is used. A logger must never propagate transport errors
|
|
335
|
+
* back into the host application.
|
|
336
|
+
*/
|
|
337
|
+
onError?: (error: Error, droppedMessages: BufferedMessage[]) => void;
|
|
217
338
|
}
|
|
218
|
-
declare abstract class BaseHttpTransport
|
|
339
|
+
declare abstract class BaseHttpTransport {
|
|
219
340
|
protected buffer: MessageBuffer;
|
|
341
|
+
private onErrorCallback?;
|
|
220
342
|
constructor(opts?: BaseHttpTransportOptions);
|
|
221
343
|
log(info: Record<string, unknown>, callback: () => void): void;
|
|
222
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Stop accepting new messages and flush what's buffered. `timeoutMs` caps
|
|
346
|
+
* how long the flush may take; after that the remaining queue is discarded
|
|
347
|
+
* so shutdown can complete. Default 5000ms.
|
|
348
|
+
*/
|
|
349
|
+
close(timeoutMs?: number): Promise<void>;
|
|
350
|
+
/** Current buffered message count and total drops since creation. */
|
|
351
|
+
getMetrics(): {
|
|
352
|
+
queueSize: number;
|
|
353
|
+
droppedTotal: number;
|
|
354
|
+
};
|
|
223
355
|
protected transformMessage(info: Record<string, unknown>): BufferedMessage;
|
|
224
356
|
protected handleError(error: Error, messages: BufferedMessage[]): void;
|
|
225
357
|
protected abstract sendBatch(messages: BufferedMessage[]): Promise<void>;
|
|
@@ -231,6 +363,7 @@ declare function matchesContext(storeContext: LoggerContext | undefined, loggerC
|
|
|
231
363
|
|
|
232
364
|
declare class DiscordTransport extends BaseHttpTransport {
|
|
233
365
|
private config;
|
|
366
|
+
private requestTimeout;
|
|
234
367
|
constructor(config: DiscordConfig);
|
|
235
368
|
protected sendBatch(messages: BufferedMessage[]): Promise<void>;
|
|
236
369
|
private sendEmbedBatch;
|
|
@@ -246,6 +379,7 @@ declare class DiscordTransport extends BaseHttpTransport {
|
|
|
246
379
|
declare class TelegramTransport extends BaseHttpTransport {
|
|
247
380
|
private config;
|
|
248
381
|
private apiUrl;
|
|
382
|
+
private requestTimeout;
|
|
249
383
|
constructor(config: TelegramConfig);
|
|
250
384
|
protected sendBatch(messages: BufferedMessage[]): Promise<void>;
|
|
251
385
|
private formatBatchMessage;
|
|
@@ -412,6 +546,7 @@ declare const LoggerConfigSchema: z.ZodObject<{
|
|
|
412
546
|
discord: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<DiscordConfig, unknown, z.core.$ZodTypeInternals<DiscordConfig, unknown>>, z.ZodArray<z.ZodType<DiscordConfig, unknown, z.core.$ZodTypeInternals<DiscordConfig, unknown>>>]>>;
|
|
413
547
|
telegram: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<TelegramConfig, unknown, z.core.$ZodTypeInternals<TelegramConfig, unknown>>, z.ZodArray<z.ZodType<TelegramConfig, unknown, z.core.$ZodTypeInternals<TelegramConfig, unknown>>>]>>;
|
|
414
548
|
cloudwatch: z.ZodOptional<z.ZodUnion<readonly [z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>, z.ZodArray<z.ZodType<CloudWatchConfig, unknown, z.core.$ZodTypeInternals<CloudWatchConfig, unknown>>>]>>;
|
|
549
|
+
relay: z.ZodOptional<z.ZodType<RelayConfig, unknown, z.core.$ZodTypeInternals<RelayConfig, unknown>>>;
|
|
415
550
|
caller: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<CallerConfig, unknown, z.core.$ZodTypeInternals<CallerConfig, unknown>>]>>;
|
|
416
551
|
hostname: z.ZodOptional<z.ZodString>;
|
|
417
552
|
autoShutdown: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodType<AutoShutdownConfig, unknown, z.core.$ZodTypeInternals<AutoShutdownConfig, unknown>>]>>;
|
|
@@ -424,6 +559,7 @@ declare function safeValidateConfig(config: unknown): z.ZodSafeParseResult<{
|
|
|
424
559
|
discord?: DiscordConfig | DiscordConfig[] | undefined;
|
|
425
560
|
telegram?: TelegramConfig | TelegramConfig[] | undefined;
|
|
426
561
|
cloudwatch?: CloudWatchConfig | CloudWatchConfig[] | undefined;
|
|
562
|
+
relay?: RelayConfig | undefined;
|
|
427
563
|
caller?: boolean | CallerConfig | undefined;
|
|
428
564
|
hostname?: string | undefined;
|
|
429
565
|
autoShutdown?: boolean | AutoShutdownConfig | undefined;
|