@logtape/logtape 1.1.3 → 1.1.5

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/src/logger.ts DELETED
@@ -1,1485 +0,0 @@
1
- import type { ContextLocalStorage } from "./context.ts";
2
- import type { Filter } from "./filter.ts";
3
- import { compareLogLevel, type LogLevel } from "./level.ts";
4
- import type { LogRecord } from "./record.ts";
5
- import type { Sink } from "./sink.ts";
6
-
7
- /**
8
- * A logger interface. It provides methods to log messages at different
9
- * severity levels.
10
- *
11
- * ```typescript
12
- * const logger = getLogger("category");
13
- * logger.trace `A trace message with ${value}`
14
- * logger.debug `A debug message with ${value}.`;
15
- * logger.info `An info message with ${value}.`;
16
- * logger.warn `A warning message with ${value}.`;
17
- * logger.error `An error message with ${value}.`;
18
- * logger.fatal `A fatal error message with ${value}.`;
19
- * ```
20
- */
21
- export interface Logger {
22
- /**
23
- * The category of the logger. It is an array of strings.
24
- */
25
- readonly category: readonly string[];
26
-
27
- /**
28
- * The logger with the supercategory of the current logger. If the current
29
- * logger is the root logger, this is `null`.
30
- */
31
- readonly parent: Logger | null;
32
-
33
- /**
34
- * Get a child logger with the given subcategory.
35
- *
36
- * ```typescript
37
- * const logger = getLogger("category");
38
- * const subLogger = logger.getChild("sub-category");
39
- * ```
40
- *
41
- * The above code is equivalent to:
42
- *
43
- * ```typescript
44
- * const logger = getLogger("category");
45
- * const subLogger = getLogger(["category", "sub-category"]);
46
- * ```
47
- *
48
- * @param subcategory The subcategory.
49
- * @returns The child logger.
50
- */
51
- getChild(
52
- subcategory: string | readonly [string] | readonly [string, ...string[]],
53
- ): Logger;
54
-
55
- /**
56
- * Get a logger with contextual properties. This is useful for
57
- * log multiple messages with the shared set of properties.
58
- *
59
- * ```typescript
60
- * const logger = getLogger("category");
61
- * const ctx = logger.with({ foo: 123, bar: "abc" });
62
- * ctx.info("A message with {foo} and {bar}.");
63
- * ctx.warn("Another message with {foo}, {bar}, and {baz}.", { baz: true });
64
- * ```
65
- *
66
- * The above code is equivalent to:
67
- *
68
- * ```typescript
69
- * const logger = getLogger("category");
70
- * logger.info("A message with {foo} and {bar}.", { foo: 123, bar: "abc" });
71
- * logger.warn(
72
- * "Another message with {foo}, {bar}, and {baz}.",
73
- * { foo: 123, bar: "abc", baz: true },
74
- * );
75
- * ```
76
- *
77
- * @param properties
78
- * @returns
79
- * @since 0.5.0
80
- */
81
- with(properties: Record<string, unknown>): Logger;
82
-
83
- /**
84
- * Log a trace message. Use this as a template string prefix.
85
- *
86
- * ```typescript
87
- * logger.trace `A trace message with ${value}.`;
88
- * ```
89
- *
90
- * @param message The message template strings array.
91
- * @param values The message template values.
92
- * @since 0.12.0
93
- */
94
- trace(message: TemplateStringsArray, ...values: readonly unknown[]): void;
95
-
96
- /**
97
- * Log a trace message with properties.
98
- *
99
- * ```typescript
100
- * logger.trace('A trace message with {value}.', { value });
101
- * ```
102
- *
103
- * If the properties are expensive to compute, you can pass a callback that
104
- * returns the properties:
105
- *
106
- * ```typescript
107
- * logger.trace(
108
- * 'A trace message with {value}.',
109
- * () => ({ value: expensiveComputation() })
110
- * );
111
- * ```
112
- *
113
- * @param message The message template. Placeholders to be replaced with
114
- * `values` are indicated by keys in curly braces (e.g.,
115
- * `{value}`).
116
- * @param properties The values to replace placeholders with. For lazy
117
- * evaluation, this can be a callback that returns the
118
- * properties.
119
- * @since 0.12.0
120
- */
121
- trace(
122
- message: string,
123
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
124
- ): void;
125
-
126
- /**
127
- * Log a trace values with no message. This is useful when you
128
- * want to log properties without a message, e.g., when you want to log
129
- * the context of a request or an operation.
130
- *
131
- * ```typescript
132
- * logger.trace({ method: 'GET', url: '/api/v1/resource' });
133
- * ```
134
- *
135
- * Note that this is a shorthand for:
136
- *
137
- * ```typescript
138
- * logger.trace('{*}', { method: 'GET', url: '/api/v1/resource' });
139
- * ```
140
- *
141
- * If the properties are expensive to compute, you cannot use this shorthand
142
- * and should use the following syntax instead:
143
- *
144
- * ```typescript
145
- * logger.trace('{*}', () => ({
146
- * method: expensiveMethod(),
147
- * url: expensiveUrl(),
148
- * }));
149
- * ```
150
- *
151
- * @param properties The values to log. Note that this does not take
152
- * a callback.
153
- * @since 0.12.0
154
- */
155
- trace(properties: Record<string, unknown>): void;
156
-
157
- /**
158
- * Lazily log a trace message. Use this when the message values are expensive
159
- * to compute and should only be computed if the message is actually logged.
160
- *
161
- * ```typescript
162
- * logger.trace(l => l`A trace message with ${expensiveValue()}.`);
163
- * ```
164
- *
165
- * @param callback A callback that returns the message template prefix.
166
- * @throws {TypeError} If no log record was made inside the callback.
167
- * @since 0.12.0
168
- */
169
- trace(callback: LogCallback): void;
170
-
171
- /**
172
- * Log a debug message. Use this as a template string prefix.
173
- *
174
- * ```typescript
175
- * logger.debug `A debug message with ${value}.`;
176
- * ```
177
- *
178
- * @param message The message template strings array.
179
- * @param values The message template values.
180
- */
181
- debug(message: TemplateStringsArray, ...values: readonly unknown[]): void;
182
-
183
- /**
184
- * Log a debug message with properties.
185
- *
186
- * ```typescript
187
- * logger.debug('A debug message with {value}.', { value });
188
- * ```
189
- *
190
- * If the properties are expensive to compute, you can pass a callback that
191
- * returns the properties:
192
- *
193
- * ```typescript
194
- * logger.debug(
195
- * 'A debug message with {value}.',
196
- * () => ({ value: expensiveComputation() })
197
- * );
198
- * ```
199
- *
200
- * @param message The message template. Placeholders to be replaced with
201
- * `values` are indicated by keys in curly braces (e.g.,
202
- * `{value}`).
203
- * @param properties The values to replace placeholders with. For lazy
204
- * evaluation, this can be a callback that returns the
205
- * properties.
206
- */
207
- debug(
208
- message: string,
209
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
210
- ): void;
211
-
212
- /**
213
- * Log a debug values with no message. This is useful when you
214
- * want to log properties without a message, e.g., when you want to log
215
- * the context of a request or an operation.
216
- *
217
- * ```typescript
218
- * logger.debug({ method: 'GET', url: '/api/v1/resource' });
219
- * ```
220
- *
221
- * Note that this is a shorthand for:
222
- *
223
- * ```typescript
224
- * logger.debug('{*}', { method: 'GET', url: '/api/v1/resource' });
225
- * ```
226
- *
227
- * If the properties are expensive to compute, you cannot use this shorthand
228
- * and should use the following syntax instead:
229
- *
230
- * ```typescript
231
- * logger.debug('{*}', () => ({
232
- * method: expensiveMethod(),
233
- * url: expensiveUrl(),
234
- * }));
235
- * ```
236
- *
237
- * @param properties The values to log. Note that this does not take
238
- * a callback.
239
- * @since 0.11.0
240
- */
241
- debug(properties: Record<string, unknown>): void;
242
-
243
- /**
244
- * Lazily log a debug message. Use this when the message values are expensive
245
- * to compute and should only be computed if the message is actually logged.
246
- *
247
- * ```typescript
248
- * logger.debug(l => l`A debug message with ${expensiveValue()}.`);
249
- * ```
250
- *
251
- * @param callback A callback that returns the message template prefix.
252
- * @throws {TypeError} If no log record was made inside the callback.
253
- */
254
- debug(callback: LogCallback): void;
255
-
256
- /**
257
- * Log an informational message. Use this as a template string prefix.
258
- *
259
- * ```typescript
260
- * logger.info `An info message with ${value}.`;
261
- * ```
262
- *
263
- * @param message The message template strings array.
264
- * @param values The message template values.
265
- */
266
- info(message: TemplateStringsArray, ...values: readonly unknown[]): void;
267
-
268
- /**
269
- * Log an informational message with properties.
270
- *
271
- * ```typescript
272
- * logger.info('An info message with {value}.', { value });
273
- * ```
274
- *
275
- * If the properties are expensive to compute, you can pass a callback that
276
- * returns the properties:
277
- *
278
- * ```typescript
279
- * logger.info(
280
- * 'An info message with {value}.',
281
- * () => ({ value: expensiveComputation() })
282
- * );
283
- * ```
284
- *
285
- * @param message The message template. Placeholders to be replaced with
286
- * `values` are indicated by keys in curly braces (e.g.,
287
- * `{value}`).
288
- * @param properties The values to replace placeholders with. For lazy
289
- * evaluation, this can be a callback that returns the
290
- * properties.
291
- */
292
- info(
293
- message: string,
294
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
295
- ): void;
296
-
297
- /**
298
- * Log an informational values with no message. This is useful when you
299
- * want to log properties without a message, e.g., when you want to log
300
- * the context of a request or an operation.
301
- *
302
- * ```typescript
303
- * logger.info({ method: 'GET', url: '/api/v1/resource' });
304
- * ```
305
- *
306
- * Note that this is a shorthand for:
307
- *
308
- * ```typescript
309
- * logger.info('{*}', { method: 'GET', url: '/api/v1/resource' });
310
- * ```
311
- *
312
- * If the properties are expensive to compute, you cannot use this shorthand
313
- * and should use the following syntax instead:
314
- *
315
- * ```typescript
316
- * logger.info('{*}', () => ({
317
- * method: expensiveMethod(),
318
- * url: expensiveUrl(),
319
- * }));
320
- * ```
321
- *
322
- * @param properties The values to log. Note that this does not take
323
- * a callback.
324
- * @since 0.11.0
325
- */
326
- info(properties: Record<string, unknown>): void;
327
-
328
- /**
329
- * Lazily log an informational message. Use this when the message values are
330
- * expensive to compute and should only be computed if the message is actually
331
- * logged.
332
- *
333
- * ```typescript
334
- * logger.info(l => l`An info message with ${expensiveValue()}.`);
335
- * ```
336
- *
337
- * @param callback A callback that returns the message template prefix.
338
- * @throws {TypeError} If no log record was made inside the callback.
339
- */
340
- info(callback: LogCallback): void;
341
-
342
- /**
343
- * Log a warning message. Use this as a template string prefix.
344
- *
345
- * ```typescript
346
- * logger.warn `A warning message with ${value}.`;
347
- * ```
348
- *
349
- * @param message The message template strings array.
350
- * @param values The message template values.
351
- */
352
- warn(message: TemplateStringsArray, ...values: readonly unknown[]): void;
353
-
354
- /**
355
- * Log a warning message with properties.
356
- *
357
- * ```typescript
358
- * logger.warn('A warning message with {value}.', { value });
359
- * ```
360
- *
361
- * If the properties are expensive to compute, you can pass a callback that
362
- * returns the properties:
363
- *
364
- * ```typescript
365
- * logger.warn(
366
- * 'A warning message with {value}.',
367
- * () => ({ value: expensiveComputation() })
368
- * );
369
- * ```
370
- *
371
- * @param message The message template. Placeholders to be replaced with
372
- * `values` are indicated by keys in curly braces (e.g.,
373
- * `{value}`).
374
- * @param properties The values to replace placeholders with. For lazy
375
- * evaluation, this can be a callback that returns the
376
- * properties.
377
- */
378
- warn(
379
- message: string,
380
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
381
- ): void;
382
-
383
- /**
384
- * Log a warning values with no message. This is useful when you
385
- * want to log properties without a message, e.g., when you want to log
386
- * the context of a request or an operation.
387
- *
388
- * ```typescript
389
- * logger.warn({ method: 'GET', url: '/api/v1/resource' });
390
- * ```
391
- *
392
- * Note that this is a shorthand for:
393
- *
394
- * ```typescript
395
- * logger.warn('{*}', { method: 'GET', url: '/api/v1/resource' });
396
- * ```
397
- *
398
- * If the properties are expensive to compute, you cannot use this shorthand
399
- * and should use the following syntax instead:
400
- *
401
- * ```typescript
402
- * logger.warn('{*}', () => ({
403
- * method: expensiveMethod(),
404
- * url: expensiveUrl(),
405
- * }));
406
- * ```
407
- *
408
- * @param properties The values to log. Note that this does not take
409
- * a callback.
410
- * @since 0.11.0
411
- */
412
- warn(properties: Record<string, unknown>): void;
413
-
414
- /**
415
- * Lazily log a warning message. Use this when the message values are
416
- * expensive to compute and should only be computed if the message is actually
417
- * logged.
418
- *
419
- * ```typescript
420
- * logger.warn(l => l`A warning message with ${expensiveValue()}.`);
421
- * ```
422
- *
423
- * @param callback A callback that returns the message template prefix.
424
- * @throws {TypeError} If no log record was made inside the callback.
425
- */
426
- warn(callback: LogCallback): void;
427
-
428
- /**
429
- * Log a warning message. Use this as a template string prefix.
430
- *
431
- * ```typescript
432
- * logger.warning `A warning message with ${value}.`;
433
- * ```
434
- *
435
- * @param message The message template strings array.
436
- * @param values The message template values.
437
- * @since 0.12.0
438
- */
439
- warning(message: TemplateStringsArray, ...values: readonly unknown[]): void;
440
-
441
- /**
442
- * Log a warning message with properties.
443
- *
444
- * ```typescript
445
- * logger.warning('A warning message with {value}.', { value });
446
- * ```
447
- *
448
- * If the properties are expensive to compute, you can pass a callback that
449
- * returns the properties:
450
- *
451
- * ```typescript
452
- * logger.warning(
453
- * 'A warning message with {value}.',
454
- * () => ({ value: expensiveComputation() })
455
- * );
456
- * ```
457
- *
458
- * @param message The message template. Placeholders to be replaced with
459
- * `values` are indicated by keys in curly braces (e.g.,
460
- * `{value}`).
461
- * @param properties The values to replace placeholders with. For lazy
462
- * evaluation, this can be a callback that returns the
463
- * properties.
464
- * @since 0.12.0
465
- */
466
- warning(
467
- message: string,
468
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
469
- ): void;
470
-
471
- /**
472
- * Log a warning values with no message. This is useful when you
473
- * want to log properties without a message, e.g., when you want to log
474
- * the context of a request or an operation.
475
- *
476
- * ```typescript
477
- * logger.warning({ method: 'GET', url: '/api/v1/resource' });
478
- * ```
479
- *
480
- * Note that this is a shorthand for:
481
- *
482
- * ```typescript
483
- * logger.warning('{*}', { method: 'GET', url: '/api/v1/resource' });
484
- * ```
485
- *
486
- * If the properties are expensive to compute, you cannot use this shorthand
487
- * and should use the following syntax instead:
488
- *
489
- * ```typescript
490
- * logger.warning('{*}', () => ({
491
- * method: expensiveMethod(),
492
- * url: expensiveUrl(),
493
- * }));
494
- * ```
495
- *
496
- * @param properties The values to log. Note that this does not take
497
- * a callback.
498
- * @since 0.12.0
499
- */
500
- warning(properties: Record<string, unknown>): void;
501
-
502
- /**
503
- * Lazily log a warning message. Use this when the message values are
504
- * expensive to compute and should only be computed if the message is actually
505
- * logged.
506
- *
507
- * ```typescript
508
- * logger.warning(l => l`A warning message with ${expensiveValue()}.`);
509
- * ```
510
- *
511
- * @param callback A callback that returns the message template prefix.
512
- * @throws {TypeError} If no log record was made inside the callback.
513
- * @since 0.12.0
514
- */
515
- warning(callback: LogCallback): void;
516
-
517
- /**
518
- * Log an error message. Use this as a template string prefix.
519
- *
520
- * ```typescript
521
- * logger.error `An error message with ${value}.`;
522
- * ```
523
- *
524
- * @param message The message template strings array.
525
- * @param values The message template values.
526
- */
527
- error(message: TemplateStringsArray, ...values: readonly unknown[]): void;
528
-
529
- /**
530
- * Log an error message with properties.
531
- *
532
- * ```typescript
533
- * logger.warn('An error message with {value}.', { value });
534
- * ```
535
- *
536
- * If the properties are expensive to compute, you can pass a callback that
537
- * returns the properties:
538
- *
539
- * ```typescript
540
- * logger.error(
541
- * 'An error message with {value}.',
542
- * () => ({ value: expensiveComputation() })
543
- * );
544
- * ```
545
- *
546
- * @param message The message template. Placeholders to be replaced with
547
- * `values` are indicated by keys in curly braces (e.g.,
548
- * `{value}`).
549
- * @param properties The values to replace placeholders with. For lazy
550
- * evaluation, this can be a callback that returns the
551
- * properties.
552
- */
553
- error(
554
- message: string,
555
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
556
- ): void;
557
-
558
- /**
559
- * Log an error values with no message. This is useful when you
560
- * want to log properties without a message, e.g., when you want to log
561
- * the context of a request or an operation.
562
- *
563
- * ```typescript
564
- * logger.error({ method: 'GET', url: '/api/v1/resource' });
565
- * ```
566
- *
567
- * Note that this is a shorthand for:
568
- *
569
- * ```typescript
570
- * logger.error('{*}', { method: 'GET', url: '/api/v1/resource' });
571
- * ```
572
- *
573
- * If the properties are expensive to compute, you cannot use this shorthand
574
- * and should use the following syntax instead:
575
- *
576
- * ```typescript
577
- * logger.error('{*}', () => ({
578
- * method: expensiveMethod(),
579
- * url: expensiveUrl(),
580
- * }));
581
- * ```
582
- *
583
- * @param properties The values to log. Note that this does not take
584
- * a callback.
585
- * @since 0.11.0
586
- */
587
- error(properties: Record<string, unknown>): void;
588
-
589
- /**
590
- * Lazily log an error message. Use this when the message values are
591
- * expensive to compute and should only be computed if the message is actually
592
- * logged.
593
- *
594
- * ```typescript
595
- * logger.error(l => l`An error message with ${expensiveValue()}.`);
596
- * ```
597
- *
598
- * @param callback A callback that returns the message template prefix.
599
- * @throws {TypeError} If no log record was made inside the callback.
600
- */
601
- error(callback: LogCallback): void;
602
-
603
- /**
604
- * Log a fatal error message. Use this as a template string prefix.
605
- *
606
- * ```typescript
607
- * logger.fatal `A fatal error message with ${value}.`;
608
- * ```
609
- *
610
- * @param message The message template strings array.
611
- * @param values The message template values.
612
- */
613
- fatal(message: TemplateStringsArray, ...values: readonly unknown[]): void;
614
-
615
- /**
616
- * Log a fatal error message with properties.
617
- *
618
- * ```typescript
619
- * logger.warn('A fatal error message with {value}.', { value });
620
- * ```
621
- *
622
- * If the properties are expensive to compute, you can pass a callback that
623
- * returns the properties:
624
- *
625
- * ```typescript
626
- * logger.fatal(
627
- * 'A fatal error message with {value}.',
628
- * () => ({ value: expensiveComputation() })
629
- * );
630
- * ```
631
- *
632
- * @param message The message template. Placeholders to be replaced with
633
- * `values` are indicated by keys in curly braces (e.g.,
634
- * `{value}`).
635
- * @param properties The values to replace placeholders with. For lazy
636
- * evaluation, this can be a callback that returns the
637
- * properties.
638
- */
639
- fatal(
640
- message: string,
641
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
642
- ): void;
643
-
644
- /**
645
- * Log a fatal error values with no message. This is useful when you
646
- * want to log properties without a message, e.g., when you want to log
647
- * the context of a request or an operation.
648
- *
649
- * ```typescript
650
- * logger.fatal({ method: 'GET', url: '/api/v1/resource' });
651
- * ```
652
- *
653
- * Note that this is a shorthand for:
654
- *
655
- * ```typescript
656
- * logger.fatal('{*}', { method: 'GET', url: '/api/v1/resource' });
657
- * ```
658
- *
659
- * If the properties are expensive to compute, you cannot use this shorthand
660
- * and should use the following syntax instead:
661
- *
662
- * ```typescript
663
- * logger.fatal('{*}', () => ({
664
- * method: expensiveMethod(),
665
- * url: expensiveUrl(),
666
- * }));
667
- * ```
668
- *
669
- * @param properties The values to log. Note that this does not take
670
- * a callback.
671
- * @since 0.11.0
672
- */
673
- fatal(properties: Record<string, unknown>): void;
674
-
675
- /**
676
- * Lazily log a fatal error message. Use this when the message values are
677
- * expensive to compute and should only be computed if the message is actually
678
- * logged.
679
- *
680
- * ```typescript
681
- * logger.fatal(l => l`A fatal error message with ${expensiveValue()}.`);
682
- * ```
683
- *
684
- * @param callback A callback that returns the message template prefix.
685
- * @throws {TypeError} If no log record was made inside the callback.
686
- */
687
- fatal(callback: LogCallback): void;
688
-
689
- /**
690
- * Emits a log record with custom fields while using this logger's
691
- * category.
692
- *
693
- * This is a low-level API for integration scenarios where you need full
694
- * control over the log record, particularly for preserving timestamps
695
- * from external systems.
696
- *
697
- * ```typescript
698
- * const logger = getLogger(["my-app", "integration"]);
699
- *
700
- * // Emit a log with a custom timestamp
701
- * logger.emit({
702
- * timestamp: kafkaLog.originalTimestamp,
703
- * level: "info",
704
- * message: [kafkaLog.message],
705
- * rawMessage: kafkaLog.message,
706
- * properties: {
707
- * source: "kafka",
708
- * partition: kafkaLog.partition,
709
- * offset: kafkaLog.offset,
710
- * },
711
- * });
712
- * ```
713
- *
714
- * @param record Log record without category field (category comes from
715
- * the logger instance)
716
- * @since 1.1.0
717
- */
718
- emit(record: Omit<LogRecord, "category">): void;
719
- }
720
-
721
- /**
722
- * A logging callback function. It is used to defer the computation of a
723
- * message template until it is actually logged.
724
- * @param prefix The message template prefix.
725
- * @returns The rendered message array.
726
- */
727
- export type LogCallback = (prefix: LogTemplatePrefix) => unknown[];
728
-
729
- /**
730
- * A logging template prefix function. It is used to log a message in
731
- * a {@link LogCallback} function.
732
- * @param message The message template strings array.
733
- * @param values The message template values.
734
- * @returns The rendered message array.
735
- */
736
- export type LogTemplatePrefix = (
737
- message: TemplateStringsArray,
738
- ...values: unknown[]
739
- ) => unknown[];
740
-
741
- /**
742
- * A function type for logging methods in the {@link Logger} interface.
743
- * @since 1.0.0
744
- */
745
- export interface LogMethod {
746
- /**
747
- * Log a message with the given level using a template string.
748
- * @param message The message template strings array.
749
- * @param values The message template values.
750
- */
751
- (
752
- message: TemplateStringsArray,
753
- ...values: readonly unknown[]
754
- ): void;
755
-
756
- /**
757
- * Log a message with the given level with properties.
758
- * @param message The message template. Placeholders to be replaced with
759
- * `values` are indicated by keys in curly braces (e.g.,
760
- * `{value}`).
761
- * @param properties The values to replace placeholders with. For lazy
762
- * evaluation, this can be a callback that returns the
763
- * properties.
764
- */
765
- (
766
- message: string,
767
- properties?: Record<string, unknown> | (() => Record<string, unknown>),
768
- ): void;
769
-
770
- /**
771
- * Log a message with the given level with no message.
772
- * @param properties The values to log. Note that this does not take
773
- * a callback.
774
- */
775
- (properties: Record<string, unknown>): void;
776
-
777
- /**
778
- * Lazily log a message with the given level.
779
- * @param callback A callback that returns the message template prefix.
780
- * @throws {TypeError} If no log record was made inside the callback.
781
- */
782
- (callback: LogCallback): void;
783
- }
784
-
785
- /**
786
- * Get a logger with the given category.
787
- *
788
- * ```typescript
789
- * const logger = getLogger(["my-app"]);
790
- * ```
791
- *
792
- * @param category The category of the logger. It can be a string or an array
793
- * of strings. If it is a string, it is equivalent to an array
794
- * with a single element.
795
- * @returns The logger.
796
- */
797
- export function getLogger(category: string | readonly string[] = []): Logger {
798
- return LoggerImpl.getLogger(category);
799
- }
800
-
801
- /**
802
- * The symbol for the global root logger.
803
- */
804
- const globalRootLoggerSymbol = Symbol.for("logtape.rootLogger");
805
-
806
- /**
807
- * The global root logger registry.
808
- */
809
- interface GlobalRootLoggerRegistry {
810
- [globalRootLoggerSymbol]?: LoggerImpl;
811
- }
812
-
813
- /**
814
- * A logger implementation. Do not use this directly; use {@link getLogger}
815
- * instead. This class is exported for testing purposes.
816
- */
817
- export class LoggerImpl implements Logger {
818
- readonly parent: LoggerImpl | null;
819
- readonly children: Record<string, LoggerImpl | WeakRef<LoggerImpl>>;
820
- readonly category: readonly string[];
821
- readonly sinks: Sink[];
822
- parentSinks: "inherit" | "override" = "inherit";
823
- readonly filters: Filter[];
824
- lowestLevel: LogLevel | null = "trace";
825
- contextLocalStorage?: ContextLocalStorage<Record<string, unknown>>;
826
-
827
- static getLogger(category: string | readonly string[] = []): LoggerImpl {
828
- let rootLogger: LoggerImpl | null = globalRootLoggerSymbol in globalThis
829
- ? ((globalThis as GlobalRootLoggerRegistry)[globalRootLoggerSymbol] ??
830
- null)
831
- : null;
832
- if (rootLogger == null) {
833
- rootLogger = new LoggerImpl(null, []);
834
- (globalThis as GlobalRootLoggerRegistry)[globalRootLoggerSymbol] =
835
- rootLogger;
836
- }
837
- if (typeof category === "string") return rootLogger.getChild(category);
838
- if (category.length === 0) return rootLogger;
839
- return rootLogger.getChild(category as readonly [string, ...string[]]);
840
- }
841
-
842
- private constructor(parent: LoggerImpl | null, category: readonly string[]) {
843
- this.parent = parent;
844
- this.children = {};
845
- this.category = category;
846
- this.sinks = [];
847
- this.filters = [];
848
- }
849
-
850
- getChild(
851
- subcategory:
852
- | string
853
- | readonly [string]
854
- | readonly [string, ...(readonly string[])],
855
- ): LoggerImpl {
856
- const name = typeof subcategory === "string" ? subcategory : subcategory[0];
857
- const childRef = this.children[name];
858
- let child: LoggerImpl | undefined = childRef instanceof LoggerImpl
859
- ? childRef
860
- : childRef?.deref();
861
- if (child == null) {
862
- child = new LoggerImpl(this, [...this.category, name]);
863
- this.children[name] = "WeakRef" in globalThis
864
- ? new WeakRef(child)
865
- : child;
866
- }
867
- if (typeof subcategory === "string" || subcategory.length === 1) {
868
- return child;
869
- }
870
- return child.getChild(
871
- subcategory.slice(1) as [string, ...(readonly string[])],
872
- );
873
- }
874
-
875
- /**
876
- * Reset the logger. This removes all sinks and filters from the logger.
877
- */
878
- reset(): void {
879
- while (this.sinks.length > 0) this.sinks.shift();
880
- this.parentSinks = "inherit";
881
- while (this.filters.length > 0) this.filters.shift();
882
- this.lowestLevel = "trace";
883
- }
884
-
885
- /**
886
- * Reset the logger and all its descendants. This removes all sinks and
887
- * filters from the logger and all its descendants.
888
- */
889
- resetDescendants(): void {
890
- for (const child of Object.values(this.children)) {
891
- const logger = child instanceof LoggerImpl ? child : child.deref();
892
- if (logger != null) logger.resetDescendants();
893
- }
894
- this.reset();
895
- }
896
-
897
- with(properties: Record<string, unknown>): Logger {
898
- return new LoggerCtx(this, { ...properties });
899
- }
900
-
901
- filter(record: LogRecord): boolean {
902
- for (const filter of this.filters) {
903
- if (!filter(record)) return false;
904
- }
905
- if (this.filters.length < 1) return this.parent?.filter(record) ?? true;
906
- return true;
907
- }
908
-
909
- *getSinks(level: LogLevel): Iterable<Sink> {
910
- if (
911
- this.lowestLevel === null || compareLogLevel(level, this.lowestLevel) < 0
912
- ) {
913
- return;
914
- }
915
- if (this.parent != null && this.parentSinks === "inherit") {
916
- for (const sink of this.parent.getSinks(level)) yield sink;
917
- }
918
- for (const sink of this.sinks) yield sink;
919
- }
920
-
921
- emit(record: Omit<LogRecord, "category">): void;
922
- emit(record: LogRecord, bypassSinks?: Set<Sink>): void;
923
- emit(
924
- record: Omit<LogRecord, "category"> | LogRecord,
925
- bypassSinks?: Set<Sink>,
926
- ): void {
927
- const fullRecord: LogRecord = "category" in record
928
- ? record as LogRecord
929
- : { ...record, category: this.category };
930
-
931
- if (
932
- this.lowestLevel === null ||
933
- compareLogLevel(fullRecord.level, this.lowestLevel) < 0 ||
934
- !this.filter(fullRecord)
935
- ) {
936
- return;
937
- }
938
- for (const sink of this.getSinks(fullRecord.level)) {
939
- if (bypassSinks?.has(sink)) continue;
940
- try {
941
- sink(fullRecord);
942
- } catch (error) {
943
- const bypassSinks2 = new Set(bypassSinks);
944
- bypassSinks2.add(sink);
945
- metaLogger.log(
946
- "fatal",
947
- "Failed to emit a log record to sink {sink}: {error}",
948
- { sink, error, record: fullRecord },
949
- bypassSinks2,
950
- );
951
- }
952
- }
953
- }
954
-
955
- log(
956
- level: LogLevel,
957
- rawMessage: string,
958
- properties: Record<string, unknown> | (() => Record<string, unknown>),
959
- bypassSinks?: Set<Sink>,
960
- ): void {
961
- const implicitContext =
962
- LoggerImpl.getLogger().contextLocalStorage?.getStore() ?? {};
963
- let cachedProps: Record<string, unknown> | undefined = undefined;
964
- const record: LogRecord = typeof properties === "function"
965
- ? {
966
- category: this.category,
967
- level,
968
- timestamp: Date.now(),
969
- get message() {
970
- return parseMessageTemplate(rawMessage, this.properties);
971
- },
972
- rawMessage,
973
- get properties() {
974
- if (cachedProps == null) {
975
- cachedProps = {
976
- ...implicitContext,
977
- ...properties(),
978
- };
979
- }
980
- return cachedProps;
981
- },
982
- }
983
- : {
984
- category: this.category,
985
- level,
986
- timestamp: Date.now(),
987
- message: parseMessageTemplate(rawMessage, {
988
- ...implicitContext,
989
- ...properties,
990
- }),
991
- rawMessage,
992
- properties: { ...implicitContext, ...properties },
993
- };
994
- this.emit(record, bypassSinks);
995
- }
996
-
997
- logLazily(
998
- level: LogLevel,
999
- callback: LogCallback,
1000
- properties: Record<string, unknown> = {},
1001
- ): void {
1002
- const implicitContext =
1003
- LoggerImpl.getLogger().contextLocalStorage?.getStore() ?? {};
1004
- let rawMessage: TemplateStringsArray | undefined = undefined;
1005
- let msg: unknown[] | undefined = undefined;
1006
- function realizeMessage(): [unknown[], TemplateStringsArray] {
1007
- if (msg == null || rawMessage == null) {
1008
- msg = callback((tpl, ...values) => {
1009
- rawMessage = tpl;
1010
- return renderMessage(tpl, values);
1011
- });
1012
- if (rawMessage == null) throw new TypeError("No log record was made.");
1013
- }
1014
- return [msg, rawMessage];
1015
- }
1016
- this.emit({
1017
- category: this.category,
1018
- level,
1019
- get message() {
1020
- return realizeMessage()[0];
1021
- },
1022
- get rawMessage() {
1023
- return realizeMessage()[1];
1024
- },
1025
- timestamp: Date.now(),
1026
- properties: { ...implicitContext, ...properties },
1027
- });
1028
- }
1029
-
1030
- logTemplate(
1031
- level: LogLevel,
1032
- messageTemplate: TemplateStringsArray,
1033
- values: unknown[],
1034
- properties: Record<string, unknown> = {},
1035
- ): void {
1036
- const implicitContext =
1037
- LoggerImpl.getLogger().contextLocalStorage?.getStore() ?? {};
1038
- this.emit({
1039
- category: this.category,
1040
- level,
1041
- message: renderMessage(messageTemplate, values),
1042
- rawMessage: messageTemplate,
1043
- timestamp: Date.now(),
1044
- properties: { ...implicitContext, ...properties },
1045
- });
1046
- }
1047
-
1048
- trace(
1049
- message:
1050
- | TemplateStringsArray
1051
- | string
1052
- | LogCallback
1053
- | Record<string, unknown>,
1054
- ...values: unknown[]
1055
- ): void {
1056
- if (typeof message === "string") {
1057
- this.log("trace", message, (values[0] ?? {}) as Record<string, unknown>);
1058
- } else if (typeof message === "function") {
1059
- this.logLazily("trace", message);
1060
- } else if (!Array.isArray(message)) {
1061
- this.log("trace", "{*}", message as Record<string, unknown>);
1062
- } else {
1063
- this.logTemplate("trace", message as TemplateStringsArray, values);
1064
- }
1065
- }
1066
-
1067
- debug(
1068
- message:
1069
- | TemplateStringsArray
1070
- | string
1071
- | LogCallback
1072
- | Record<string, unknown>,
1073
- ...values: unknown[]
1074
- ): void {
1075
- if (typeof message === "string") {
1076
- this.log("debug", message, (values[0] ?? {}) as Record<string, unknown>);
1077
- } else if (typeof message === "function") {
1078
- this.logLazily("debug", message);
1079
- } else if (!Array.isArray(message)) {
1080
- this.log("debug", "{*}", message as Record<string, unknown>);
1081
- } else {
1082
- this.logTemplate("debug", message as TemplateStringsArray, values);
1083
- }
1084
- }
1085
-
1086
- info(
1087
- message:
1088
- | TemplateStringsArray
1089
- | string
1090
- | LogCallback
1091
- | Record<string, unknown>,
1092
- ...values: unknown[]
1093
- ): void {
1094
- if (typeof message === "string") {
1095
- this.log("info", message, (values[0] ?? {}) as Record<string, unknown>);
1096
- } else if (typeof message === "function") {
1097
- this.logLazily("info", message);
1098
- } else if (!Array.isArray(message)) {
1099
- this.log("info", "{*}", message as Record<string, unknown>);
1100
- } else {
1101
- this.logTemplate("info", message as TemplateStringsArray, values);
1102
- }
1103
- }
1104
-
1105
- warn(
1106
- message:
1107
- | TemplateStringsArray
1108
- | string
1109
- | LogCallback
1110
- | Record<string, unknown>,
1111
- ...values: unknown[]
1112
- ): void {
1113
- if (typeof message === "string") {
1114
- this.log(
1115
- "warning",
1116
- message,
1117
- (values[0] ?? {}) as Record<string, unknown>,
1118
- );
1119
- } else if (typeof message === "function") {
1120
- this.logLazily("warning", message);
1121
- } else if (!Array.isArray(message)) {
1122
- this.log("warning", "{*}", message as Record<string, unknown>);
1123
- } else {
1124
- this.logTemplate("warning", message as TemplateStringsArray, values);
1125
- }
1126
- }
1127
-
1128
- warning(
1129
- message:
1130
- | TemplateStringsArray
1131
- | string
1132
- | LogCallback
1133
- | Record<string, unknown>,
1134
- ...values: unknown[]
1135
- ): void {
1136
- this.warn(message, ...values);
1137
- }
1138
-
1139
- error(
1140
- message:
1141
- | TemplateStringsArray
1142
- | string
1143
- | LogCallback
1144
- | Record<string, unknown>,
1145
- ...values: unknown[]
1146
- ): void {
1147
- if (typeof message === "string") {
1148
- this.log("error", message, (values[0] ?? {}) as Record<string, unknown>);
1149
- } else if (typeof message === "function") {
1150
- this.logLazily("error", message);
1151
- } else if (!Array.isArray(message)) {
1152
- this.log("error", "{*}", message as Record<string, unknown>);
1153
- } else {
1154
- this.logTemplate("error", message as TemplateStringsArray, values);
1155
- }
1156
- }
1157
-
1158
- fatal(
1159
- message:
1160
- | TemplateStringsArray
1161
- | string
1162
- | LogCallback
1163
- | Record<string, unknown>,
1164
- ...values: unknown[]
1165
- ): void {
1166
- if (typeof message === "string") {
1167
- this.log("fatal", message, (values[0] ?? {}) as Record<string, unknown>);
1168
- } else if (typeof message === "function") {
1169
- this.logLazily("fatal", message);
1170
- } else if (!Array.isArray(message)) {
1171
- this.log("fatal", "{*}", message as Record<string, unknown>);
1172
- } else {
1173
- this.logTemplate("fatal", message as TemplateStringsArray, values);
1174
- }
1175
- }
1176
- }
1177
-
1178
- /**
1179
- * A logger implementation with contextual properties. Do not use this
1180
- * directly; use {@link Logger.with} instead. This class is exported
1181
- * for testing purposes.
1182
- */
1183
- export class LoggerCtx implements Logger {
1184
- logger: LoggerImpl;
1185
- properties: Record<string, unknown>;
1186
-
1187
- constructor(logger: LoggerImpl, properties: Record<string, unknown>) {
1188
- this.logger = logger;
1189
- this.properties = properties;
1190
- }
1191
-
1192
- get category(): readonly string[] {
1193
- return this.logger.category;
1194
- }
1195
-
1196
- get parent(): Logger | null {
1197
- return this.logger.parent;
1198
- }
1199
-
1200
- getChild(
1201
- subcategory: string | readonly [string] | readonly [string, ...string[]],
1202
- ): Logger {
1203
- return this.logger.getChild(subcategory).with(this.properties);
1204
- }
1205
-
1206
- with(properties: Record<string, unknown>): Logger {
1207
- return new LoggerCtx(this.logger, { ...this.properties, ...properties });
1208
- }
1209
-
1210
- log(
1211
- level: LogLevel,
1212
- message: string,
1213
- properties: Record<string, unknown> | (() => Record<string, unknown>),
1214
- bypassSinks?: Set<Sink>,
1215
- ): void {
1216
- this.logger.log(
1217
- level,
1218
- message,
1219
- typeof properties === "function"
1220
- ? () => ({
1221
- ...this.properties,
1222
- ...properties(),
1223
- })
1224
- : { ...this.properties, ...properties },
1225
- bypassSinks,
1226
- );
1227
- }
1228
-
1229
- logLazily(level: LogLevel, callback: LogCallback): void {
1230
- this.logger.logLazily(level, callback, this.properties);
1231
- }
1232
-
1233
- logTemplate(
1234
- level: LogLevel,
1235
- messageTemplate: TemplateStringsArray,
1236
- values: unknown[],
1237
- ): void {
1238
- this.logger.logTemplate(level, messageTemplate, values, this.properties);
1239
- }
1240
-
1241
- emit(record: Omit<LogRecord, "category">): void {
1242
- const recordWithContext = {
1243
- ...record,
1244
- properties: { ...this.properties, ...record.properties },
1245
- };
1246
- this.logger.emit(recordWithContext);
1247
- }
1248
-
1249
- trace(
1250
- message:
1251
- | TemplateStringsArray
1252
- | string
1253
- | LogCallback
1254
- | Record<string, unknown>,
1255
- ...values: unknown[]
1256
- ): void {
1257
- if (typeof message === "string") {
1258
- this.log("trace", message, (values[0] ?? {}) as Record<string, unknown>);
1259
- } else if (typeof message === "function") {
1260
- this.logLazily("trace", message);
1261
- } else if (!Array.isArray(message)) {
1262
- this.log("trace", "{*}", message as Record<string, unknown>);
1263
- } else {
1264
- this.logTemplate("trace", message as TemplateStringsArray, values);
1265
- }
1266
- }
1267
-
1268
- debug(
1269
- message:
1270
- | TemplateStringsArray
1271
- | string
1272
- | LogCallback
1273
- | Record<string, unknown>,
1274
- ...values: unknown[]
1275
- ): void {
1276
- if (typeof message === "string") {
1277
- this.log("debug", message, (values[0] ?? {}) as Record<string, unknown>);
1278
- } else if (typeof message === "function") {
1279
- this.logLazily("debug", message);
1280
- } else if (!Array.isArray(message)) {
1281
- this.log("debug", "{*}", message as Record<string, unknown>);
1282
- } else {
1283
- this.logTemplate("debug", message as TemplateStringsArray, values);
1284
- }
1285
- }
1286
-
1287
- info(
1288
- message:
1289
- | TemplateStringsArray
1290
- | string
1291
- | LogCallback
1292
- | Record<string, unknown>,
1293
- ...values: unknown[]
1294
- ): void {
1295
- if (typeof message === "string") {
1296
- this.log("info", message, (values[0] ?? {}) as Record<string, unknown>);
1297
- } else if (typeof message === "function") {
1298
- this.logLazily("info", message);
1299
- } else if (!Array.isArray(message)) {
1300
- this.log("info", "{*}", message as Record<string, unknown>);
1301
- } else {
1302
- this.logTemplate("info", message as TemplateStringsArray, values);
1303
- }
1304
- }
1305
-
1306
- warn(
1307
- message:
1308
- | TemplateStringsArray
1309
- | string
1310
- | LogCallback
1311
- | Record<string, unknown>,
1312
- ...values: unknown[]
1313
- ): void {
1314
- if (typeof message === "string") {
1315
- this.log(
1316
- "warning",
1317
- message,
1318
- (values[0] ?? {}) as Record<string, unknown>,
1319
- );
1320
- } else if (typeof message === "function") {
1321
- this.logLazily("warning", message);
1322
- } else if (!Array.isArray(message)) {
1323
- this.log("warning", "{*}", message as Record<string, unknown>);
1324
- } else {
1325
- this.logTemplate("warning", message as TemplateStringsArray, values);
1326
- }
1327
- }
1328
-
1329
- warning(
1330
- message:
1331
- | TemplateStringsArray
1332
- | string
1333
- | LogCallback
1334
- | Record<string, unknown>,
1335
- ...values: unknown[]
1336
- ): void {
1337
- this.warn(message, ...values);
1338
- }
1339
-
1340
- error(
1341
- message:
1342
- | TemplateStringsArray
1343
- | string
1344
- | LogCallback
1345
- | Record<string, unknown>,
1346
- ...values: unknown[]
1347
- ): void {
1348
- if (typeof message === "string") {
1349
- this.log("error", message, (values[0] ?? {}) as Record<string, unknown>);
1350
- } else if (typeof message === "function") {
1351
- this.logLazily("error", message);
1352
- } else if (!Array.isArray(message)) {
1353
- this.log("error", "{*}", message as Record<string, unknown>);
1354
- } else {
1355
- this.logTemplate("error", message as TemplateStringsArray, values);
1356
- }
1357
- }
1358
-
1359
- fatal(
1360
- message:
1361
- | TemplateStringsArray
1362
- | string
1363
- | LogCallback
1364
- | Record<string, unknown>,
1365
- ...values: unknown[]
1366
- ): void {
1367
- if (typeof message === "string") {
1368
- this.log("fatal", message, (values[0] ?? {}) as Record<string, unknown>);
1369
- } else if (typeof message === "function") {
1370
- this.logLazily("fatal", message);
1371
- } else if (!Array.isArray(message)) {
1372
- this.log("fatal", "{*}", message as Record<string, unknown>);
1373
- } else {
1374
- this.logTemplate("fatal", message as TemplateStringsArray, values);
1375
- }
1376
- }
1377
- }
1378
-
1379
- /**
1380
- * The meta logger. It is a logger with the category `["logtape", "meta"]`.
1381
- */
1382
- const metaLogger = LoggerImpl.getLogger(["logtape", "meta"]);
1383
-
1384
- /**
1385
- * Parse a message template into a message template array and a values array.
1386
- * @param template The message template.
1387
- * @param properties The values to replace placeholders with.
1388
- * @returns The message template array and the values array.
1389
- */
1390
- export function parseMessageTemplate(
1391
- template: string,
1392
- properties: Record<string, unknown>,
1393
- ): readonly unknown[] {
1394
- const length = template.length;
1395
- if (length === 0) return [""];
1396
-
1397
- // Fast path: no placeholders
1398
- if (!template.includes("{")) return [template];
1399
-
1400
- const message: unknown[] = [];
1401
- let startIndex = 0;
1402
-
1403
- for (let i = 0; i < length; i++) {
1404
- const char = template[i];
1405
-
1406
- if (char === "{") {
1407
- const nextChar = i + 1 < length ? template[i + 1] : "";
1408
-
1409
- if (nextChar === "{") {
1410
- // Escaped { character - skip and continue
1411
- i++; // Skip the next {
1412
- continue;
1413
- }
1414
-
1415
- // Find the closing }
1416
- const closeIndex = template.indexOf("}", i + 1);
1417
- if (closeIndex === -1) {
1418
- // No closing } found, treat as literal text
1419
- continue;
1420
- }
1421
-
1422
- // Add text before placeholder
1423
- const beforeText = template.slice(startIndex, i);
1424
- message.push(beforeText.replace(/{{/g, "{").replace(/}}/g, "}"));
1425
-
1426
- // Extract and process placeholder key
1427
- const key = template.slice(i + 1, closeIndex);
1428
-
1429
- // Resolve property value
1430
- let prop: unknown;
1431
-
1432
- // Check for wildcard patterns
1433
- const trimmedKey = key.trim();
1434
- if (trimmedKey === "*") {
1435
- // This is a wildcard pattern
1436
- prop = key in properties
1437
- ? properties[key]
1438
- : "*" in properties
1439
- ? properties["*"]
1440
- : properties;
1441
- } else {
1442
- // Regular property lookup with possible whitespace handling
1443
- if (key !== trimmedKey) {
1444
- // Key has leading/trailing whitespace
1445
- prop = key in properties ? properties[key] : properties[trimmedKey];
1446
- } else {
1447
- // Key has no leading/trailing whitespace
1448
- prop = properties[key];
1449
- }
1450
- }
1451
-
1452
- message.push(prop);
1453
- i = closeIndex; // Move to the }
1454
- startIndex = i + 1;
1455
- } else if (char === "}" && i + 1 < length && template[i + 1] === "}") {
1456
- // Escaped } character - skip
1457
- i++; // Skip the next }
1458
- }
1459
- }
1460
-
1461
- // Add remaining text
1462
- const remainingText = template.slice(startIndex);
1463
- message.push(remainingText.replace(/{{/g, "{").replace(/}}/g, "}"));
1464
-
1465
- return message;
1466
- }
1467
-
1468
- /**
1469
- * Render a message template with values.
1470
- * @param template The message template.
1471
- * @param values The message template values.
1472
- * @returns The message template values interleaved between the substitution
1473
- * values.
1474
- */
1475
- export function renderMessage(
1476
- template: TemplateStringsArray,
1477
- values: readonly unknown[],
1478
- ): unknown[] {
1479
- const args = [];
1480
- for (let i = 0; i < template.length; i++) {
1481
- args.push(template[i]);
1482
- if (i < values.length) args.push(values[i]);
1483
- }
1484
- return args;
1485
- }