@logtape/logtape 1.1.4 → 1.1.6

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.
@@ -1,1052 +0,0 @@
1
- import { suite } from "@alinea/suite";
2
- import { assert } from "@std/assert/assert";
3
- import { assertEquals } from "@std/assert/equals";
4
- import { assertFalse } from "@std/assert/false";
5
- import { assertGreaterOrEqual } from "@std/assert/greater-or-equal";
6
- import { assertLessOrEqual } from "@std/assert/less-or-equal";
7
- import { assertStrictEquals } from "@std/assert/strict-equals";
8
- import { toFilter } from "./filter.ts";
9
- import { debug, error, info, warning } from "./fixtures.ts";
10
- import {
11
- getLogger,
12
- LoggerCtx,
13
- LoggerImpl,
14
- type LogMethod,
15
- parseMessageTemplate,
16
- renderMessage,
17
- } from "./logger.ts";
18
- import type { LogRecord } from "./record.ts";
19
- import type { Sink } from "./sink.ts";
20
-
21
- const test = suite(import.meta);
22
-
23
- function templateLiteral(tpl: TemplateStringsArray, ..._: unknown[]) {
24
- return tpl;
25
- }
26
-
27
- test("getLogger()", () => {
28
- assertEquals(getLogger().category, []);
29
- assertStrictEquals(getLogger(), getLogger());
30
- assertStrictEquals(getLogger([]), getLogger());
31
- assertEquals(getLogger("foo").category, ["foo"]);
32
- assertStrictEquals(getLogger("foo"), getLogger("foo"));
33
- assertStrictEquals(getLogger("foo"), getLogger(["foo"]));
34
- assertStrictEquals(getLogger("foo"), getLogger().getChild("foo"));
35
- assertEquals(getLogger(["foo", "bar"]).category, ["foo", "bar"]);
36
- assertStrictEquals(
37
- getLogger(["foo", "bar"]),
38
- getLogger().getChild(["foo", "bar"]),
39
- );
40
- assertStrictEquals(
41
- getLogger(["foo", "bar"]),
42
- getLogger().getChild("foo").getChild("bar"),
43
- );
44
- });
45
-
46
- test("Logger.getChild()", () => {
47
- const foo = getLogger("foo");
48
- const fooBar = foo.getChild("bar");
49
- assertEquals(fooBar.category, ["foo", "bar"]);
50
- assertStrictEquals(fooBar.parent, foo);
51
- const fooBarBaz = foo.getChild(["bar", "baz"]);
52
- assertEquals(fooBarBaz.category, ["foo", "bar", "baz"]);
53
- assertEquals(fooBarBaz.parent, fooBar);
54
-
55
- const fooCtx = foo.with({ a: 1, b: 2 });
56
- const fooBarCtx = fooCtx.getChild("bar");
57
- assertEquals(fooBarCtx.category, ["foo", "bar"]);
58
- // @ts-ignore: internal attribute:
59
- assertEquals(fooBarCtx.properties, { a: 1, b: 2 });
60
- });
61
-
62
- test("Logger.with()", () => {
63
- const foo = getLogger("foo");
64
- const ctx = foo.with({ a: 1, b: 2 });
65
- assertEquals(ctx.parent, getLogger());
66
- assertEquals(ctx.category, ["foo"]);
67
- // @ts-ignore: internal attribute:
68
- assertEquals(ctx.properties, { a: 1, b: 2 });
69
- // @ts-ignore: internal attribute:
70
- assertEquals(ctx.with({ c: 3 }).properties, { a: 1, b: 2, c: 3 });
71
- });
72
-
73
- test("LoggerImpl.filter()", () => {
74
- const root = LoggerImpl.getLogger([]);
75
- const foo = LoggerImpl.getLogger("foo");
76
- const fooBar = foo.getChild("bar");
77
- const fooBaz = foo.getChild("baz");
78
- const fooBarQux = fooBar.getChild("qux");
79
- const fooQuux = foo.getChild("quux");
80
-
81
- try {
82
- foo.filters.push((log) => log.level === "info");
83
- fooBar.filters.push((log) => log.message.includes("!"));
84
- fooBaz.filters.push((log) => log.message.includes("."));
85
- fooBarQux.filters.push(() => true);
86
- assert(root.filter(info));
87
- assert(foo.filter(info));
88
- assert(fooBar.filter(info));
89
- assertFalse(fooBaz.filter(info));
90
- assert(fooBarQux.filter(info));
91
- assert(fooQuux.filter(info));
92
- assert(root.filter(debug));
93
- assertFalse(foo.filter(debug));
94
- assert(fooBar.filter(debug));
95
- assertFalse(fooBaz.filter(debug));
96
- assert(fooBarQux.filter(debug));
97
- assertFalse(fooQuux.filter(debug));
98
- } finally {
99
- root.resetDescendants();
100
- }
101
- });
102
-
103
- test("LoggerImpl.getSinks()", () => {
104
- const root = LoggerImpl.getLogger([]);
105
- const foo = LoggerImpl.getLogger("foo");
106
- const fooBar = foo.getChild("bar");
107
- const fooBaz = foo.getChild("baz");
108
- const fooBarQux = fooBar.getChild("qux");
109
-
110
- try {
111
- const sinkA: Sink = () => {};
112
- foo.sinks.push(sinkA);
113
- const sinkB: Sink = () => {};
114
- fooBar.sinks.push(sinkB);
115
- const sinkC: Sink = () => {};
116
- fooBaz.sinks.push(sinkC);
117
- const sinkD: Sink = () => {};
118
- fooBarQux.sinks.push(sinkD);
119
- assertEquals([...root.getSinks("debug")], []);
120
- assertEquals([...foo.getSinks("debug")], [sinkA]);
121
- assertEquals([...fooBar.getSinks("debug")], [sinkA, sinkB]);
122
- assertEquals([...fooBaz.getSinks("debug")], [sinkA, sinkC]);
123
- assertEquals([...fooBarQux.getSinks("debug")], [sinkA, sinkB, sinkD]);
124
- fooBarQux.parentSinks = "override";
125
- assertEquals([...fooBarQux.getSinks("debug")], [sinkD]);
126
- } finally {
127
- root.resetDescendants();
128
- }
129
- });
130
-
131
- test("LoggerImpl.emit()", () => {
132
- const root = LoggerImpl.getLogger([]);
133
- const foo = root.getChild("foo");
134
- const fooBar = foo.getChild("bar");
135
- const fooBarBaz = fooBar.getChild("baz");
136
- const fooQux = foo.getChild("qux");
137
-
138
- const rootRecords: LogRecord[] = [];
139
- root.sinks.push(rootRecords.push.bind(rootRecords));
140
- root.filters.push(toFilter("warning"));
141
- const fooRecords: LogRecord[] = [];
142
- foo.sinks.push(fooRecords.push.bind(fooRecords));
143
- foo.filters.push(toFilter("info"));
144
- const fooBarRecords: LogRecord[] = [];
145
- fooBar.sinks.push(fooBarRecords.push.bind(fooBarRecords));
146
- fooBar.filters.push(toFilter("error"));
147
- const fooQuxRecords: LogRecord[] = [];
148
- fooQux.sinks.push(fooQuxRecords.push.bind(fooQuxRecords));
149
-
150
- try {
151
- root.emit(info);
152
- assertEquals(rootRecords, []);
153
- assertEquals(fooRecords, []);
154
- assertEquals(fooBarRecords, []);
155
- root.emit(warning);
156
- assertEquals(rootRecords, [warning]);
157
- assertEquals(fooRecords, []);
158
- assertEquals(fooBarRecords, []);
159
-
160
- foo.emit(debug);
161
- assertEquals(rootRecords, [warning]);
162
- assertEquals(fooRecords, []);
163
- assertEquals(fooBarRecords, []);
164
- foo.emit(info);
165
- assertEquals(rootRecords, [warning, info]);
166
- assertEquals(fooRecords, [info]);
167
- assertEquals(fooBarRecords, []);
168
-
169
- fooBar.emit(warning);
170
- assertEquals(rootRecords, [warning, info]);
171
- assertEquals(fooRecords, [info]);
172
- assertEquals(fooBarRecords, []);
173
- fooBar.emit(error);
174
- assertEquals(rootRecords, [warning, info, error]);
175
- assertEquals(fooRecords, [info, error]);
176
- assertEquals(fooBarRecords, [error]);
177
- } finally {
178
- while (rootRecords.length > 0) rootRecords.pop();
179
- while (fooRecords.length > 0) fooRecords.pop();
180
- while (fooBarRecords.length > 0) fooBarRecords.pop();
181
- }
182
-
183
- const errorSink: Sink = () => {
184
- throw new Error("This is an error");
185
- };
186
- fooBarBaz.sinks.push(errorSink);
187
-
188
- try {
189
- fooBarBaz.emit(error);
190
- assertEquals(rootRecords.length, 2);
191
- assertEquals(rootRecords[0], error);
192
- assertEquals(fooRecords, [error]);
193
- assertEquals(fooBarRecords, [error]);
194
- assertEquals(rootRecords[1].category, ["logtape", "meta"]);
195
- assertEquals(rootRecords[1].level, "fatal");
196
- assertEquals(rootRecords[1].message, [
197
- "Failed to emit a log record to sink ",
198
- errorSink,
199
- ": ",
200
- rootRecords[1].properties.error,
201
- "",
202
- ]);
203
- assertEquals(rootRecords[1].properties, {
204
- record: error,
205
- sink: errorSink,
206
- error: rootRecords[1].properties.error,
207
- });
208
-
209
- root.sinks.push(errorSink);
210
- fooBarBaz.emit(error);
211
- } finally {
212
- while (rootRecords.length > 0) rootRecords.pop();
213
- while (fooRecords.length > 0) fooRecords.pop();
214
- while (fooBarRecords.length > 0) fooBarRecords.pop();
215
- while (root.filters.length > 0) root.filters.pop();
216
- while (foo.filters.length > 0) foo.filters.pop();
217
- while (fooBar.filters.length > 0) fooBar.filters.pop();
218
- root.sinks.pop();
219
- }
220
-
221
- root.lowestLevel = "debug";
222
- foo.lowestLevel = "error";
223
- fooBar.lowestLevel = "info";
224
-
225
- try {
226
- fooBar.emit({ ...debug, category: ["foo", "bar"] });
227
- assertEquals(rootRecords, []);
228
- assertEquals(fooRecords, []);
229
- assertEquals(fooBarRecords, []);
230
-
231
- const debugRecord = { ...debug, category: ["foo", "qux"] };
232
- fooQux.emit(debugRecord);
233
- assertEquals(rootRecords, []);
234
- assertEquals(fooRecords, []);
235
- assertEquals(fooQuxRecords, [debugRecord]);
236
-
237
- foo.emit({ ...debug, category: ["foo"] });
238
- assertEquals(rootRecords, []);
239
- assertEquals(fooRecords, []);
240
-
241
- const debugRecord2 = { ...debug, category: [] };
242
- root.emit(debugRecord2);
243
- assertEquals(rootRecords, [debugRecord2]);
244
-
245
- const infoRecord = { ...info, category: ["foo", "bar"] };
246
- fooBar.emit(infoRecord);
247
- assertEquals(rootRecords, [debugRecord2]);
248
- assertEquals(fooRecords, []);
249
- assertEquals(fooBarRecords, [infoRecord]);
250
- } finally {
251
- root.resetDescendants();
252
- }
253
- });
254
-
255
- test("LoggerImpl.log()", () => {
256
- const logger = LoggerImpl.getLogger("foo");
257
-
258
- try {
259
- const logs: LogRecord[] = [];
260
- logger.sinks.push(logs.push.bind(logs));
261
- const before = Date.now();
262
- logger.log("info", "Hello, {foo}!", { foo: 123 });
263
- const after = Date.now();
264
- assertEquals(logs, [
265
- {
266
- category: ["foo"],
267
- level: "info",
268
- message: ["Hello, ", 123, "!"],
269
- rawMessage: "Hello, {foo}!",
270
- timestamp: logs[0].timestamp,
271
- properties: { foo: 123 },
272
- },
273
- ]);
274
- assertGreaterOrEqual(logs[0].timestamp, before);
275
- assertLessOrEqual(logs[0].timestamp, after);
276
-
277
- logs.shift();
278
- logger.filters.push(toFilter("error"));
279
- let called = 0;
280
- logger.log("warning", "Hello, {foo}!", () => {
281
- called++;
282
- return { foo: 123 };
283
- });
284
- assertEquals(logs, []);
285
- assertEquals(called, 0);
286
-
287
- logger.log("error", "Hello, {foo}!", () => {
288
- called++;
289
- return { foo: 123 };
290
- });
291
- assertEquals(logs, [
292
- {
293
- category: ["foo"],
294
- level: "error",
295
- message: ["Hello, ", 123, "!"],
296
- rawMessage: "Hello, {foo}!",
297
- timestamp: logs[0].timestamp,
298
- properties: { foo: 123 },
299
- },
300
- ]);
301
- assertEquals(called, 1);
302
- } finally {
303
- logger.resetDescendants();
304
- }
305
- });
306
-
307
- test("LoggerImpl.logLazily()", () => {
308
- const logger = LoggerImpl.getLogger("foo");
309
-
310
- let called = 0;
311
- function calc() {
312
- called++;
313
- return 123;
314
- }
315
-
316
- try {
317
- const logs: LogRecord[] = [];
318
- logger.sinks.push(logs.push.bind(logs));
319
- logger.filters.push(toFilter("error"));
320
- logger.logLazily("warning", (l) => l`Hello, ${calc()}!`);
321
- assertEquals(logs, []);
322
- assertEquals(called, 0);
323
-
324
- const before = Date.now();
325
- logger.logLazily("error", (l) => l`Hello, ${calc()}!`);
326
- const after = Date.now();
327
- assertEquals(logs, [
328
- {
329
- category: ["foo"],
330
- level: "error",
331
- message: ["Hello, ", 123, "!"],
332
- rawMessage: templateLiteral`Hello, ${null}!`,
333
- timestamp: logs[0].timestamp,
334
- properties: {},
335
- },
336
- ]);
337
- assertGreaterOrEqual(logs[0].timestamp, before);
338
- assertLessOrEqual(logs[0].timestamp, after);
339
- assertEquals(called, 1);
340
- } finally {
341
- logger.resetDescendants();
342
- }
343
- });
344
-
345
- test("LoggerImpl.logTemplate()", () => {
346
- const logger = LoggerImpl.getLogger("foo");
347
-
348
- function info(tpl: TemplateStringsArray, ...values: unknown[]) {
349
- logger.logTemplate("info", tpl, values);
350
- }
351
-
352
- try {
353
- const logs: LogRecord[] = [];
354
- logger.sinks.push(logs.push.bind(logs));
355
-
356
- const before = Date.now();
357
- info`Hello, ${123}!`;
358
- const after = Date.now();
359
- assertEquals(logs, [
360
- {
361
- category: ["foo"],
362
- level: "info",
363
- message: ["Hello, ", 123, "!"],
364
- rawMessage: templateLiteral`Hello, ${null}!`,
365
- timestamp: logs[0].timestamp,
366
- properties: {},
367
- },
368
- ]);
369
- assertGreaterOrEqual(logs[0].timestamp, before);
370
- assertLessOrEqual(logs[0].timestamp, after);
371
- } finally {
372
- logger.resetDescendants();
373
- }
374
- });
375
-
376
- test("LoggerCtx.log()", () => {
377
- const logger = LoggerImpl.getLogger("foo");
378
- const ctx = new LoggerCtx(logger, { a: 1, b: 2 });
379
-
380
- try {
381
- const logs: LogRecord[] = [];
382
- logger.sinks.push(logs.push.bind(logs));
383
- const before = Date.now();
384
- ctx.log("info", "Hello, {a} {b} {c}!", { c: 3 });
385
- const after = Date.now();
386
- assertEquals(logs, [
387
- {
388
- category: ["foo"],
389
- level: "info",
390
- message: ["Hello, ", 1, " ", 2, " ", 3, "!"],
391
- rawMessage: "Hello, {a} {b} {c}!",
392
- timestamp: logs[0].timestamp,
393
- properties: { a: 1, b: 2, c: 3 },
394
- },
395
- ]);
396
- assertGreaterOrEqual(logs[0].timestamp, before);
397
- assertLessOrEqual(logs[0].timestamp, after);
398
-
399
- logs.shift();
400
- logger.filters.push(toFilter("error"));
401
- let called = 0;
402
- ctx.log("warning", "Hello, {a} {b} {c}!", () => {
403
- called++;
404
- return { c: 3 };
405
- });
406
- assertEquals(logs, []);
407
- assertEquals(called, 0);
408
-
409
- ctx.log("error", "Hello, {a} {b} {c}!", () => {
410
- called++;
411
- return { c: 3 };
412
- });
413
- assertEquals(logs, [
414
- {
415
- category: ["foo"],
416
- level: "error",
417
- message: ["Hello, ", 1, " ", 2, " ", 3, "!"],
418
- rawMessage: "Hello, {a} {b} {c}!",
419
- timestamp: logs[0].timestamp,
420
- properties: { a: 1, b: 2, c: 3 },
421
- },
422
- ]);
423
- assertEquals(called, 1);
424
- } finally {
425
- logger.resetDescendants();
426
- }
427
- });
428
-
429
- test("LoggerCtx.logLazily()", () => {
430
- const logger = LoggerImpl.getLogger("foo");
431
- const ctx = new LoggerCtx(logger, { a: 1, b: 2 });
432
-
433
- let called = 0;
434
- function calc() {
435
- called++;
436
- return 123;
437
- }
438
-
439
- try {
440
- const logs: LogRecord[] = [];
441
- logger.sinks.push(logs.push.bind(logs));
442
- logger.filters.push(toFilter("error"));
443
- logger.logLazily("warning", (l) => l`Hello, ${calc()}!`);
444
- assertEquals(logs, []);
445
- assertEquals(called, 0);
446
-
447
- const before = Date.now();
448
- ctx.logLazily("error", (l) => l`Hello, ${calc()}!`);
449
- const after = Date.now();
450
- assertEquals(logs, [
451
- {
452
- category: ["foo"],
453
- level: "error",
454
- message: ["Hello, ", 123, "!"],
455
- rawMessage: templateLiteral`Hello, ${null}!`,
456
- timestamp: logs[0].timestamp,
457
- properties: { a: 1, b: 2 },
458
- },
459
- ]);
460
- assertGreaterOrEqual(logs[0].timestamp, before);
461
- assertLessOrEqual(logs[0].timestamp, after);
462
- assertEquals(called, 1);
463
- } finally {
464
- logger.resetDescendants();
465
- }
466
- });
467
-
468
- test("LoggerCtx.logTemplate()", () => {
469
- const logger = LoggerImpl.getLogger("foo");
470
- const ctx = new LoggerCtx(logger, { a: 1, b: 2 });
471
-
472
- function info(tpl: TemplateStringsArray, ...values: unknown[]) {
473
- ctx.logTemplate("info", tpl, values);
474
- }
475
-
476
- try {
477
- const logs: LogRecord[] = [];
478
- logger.sinks.push(logs.push.bind(logs));
479
-
480
- const before = Date.now();
481
- info`Hello, ${123}!`;
482
- const after = Date.now();
483
- assertEquals(logs, [
484
- {
485
- category: ["foo"],
486
- level: "info",
487
- message: ["Hello, ", 123, "!"],
488
- rawMessage: templateLiteral`Hello, ${null}!`,
489
- timestamp: logs[0].timestamp,
490
- properties: { a: 1, b: 2 },
491
- },
492
- ]);
493
- assertGreaterOrEqual(logs[0].timestamp, before);
494
- assertLessOrEqual(logs[0].timestamp, after);
495
- } finally {
496
- logger.resetDescendants();
497
- }
498
- });
499
-
500
- const methods = [
501
- "trace",
502
- "debug",
503
- "info",
504
- "warn",
505
- "warning",
506
- "error",
507
- "fatal",
508
- ] as const;
509
-
510
- for (const method of methods) {
511
- test(`Logger.${method}() [template]`, () => {
512
- const logger = LoggerImpl.getLogger("foo");
513
- const ctx = new LoggerCtx(logger, { a: 1, b: 2 });
514
-
515
- function tpl(tpl: TemplateStringsArray, ...values: unknown[]) {
516
- logger[method](tpl, ...values);
517
- }
518
-
519
- const logs: LogRecord[] = [];
520
- try {
521
- logger.sinks.push(logs.push.bind(logs));
522
- const before = Date.now();
523
- tpl`Hello, ${123}!`;
524
- const after = Date.now();
525
- assertEquals(logs, [
526
- {
527
- category: ["foo"],
528
- level: method === "warn" ? "warning" : method,
529
- message: ["Hello, ", 123, "!"],
530
- rawMessage: templateLiteral`Hello, ${null}!`,
531
- timestamp: logs[0].timestamp,
532
- properties: {},
533
- },
534
- ]);
535
- assertGreaterOrEqual(logs[0].timestamp, before);
536
- assertLessOrEqual(logs[0].timestamp, after);
537
- } finally {
538
- logs.shift();
539
- }
540
-
541
- function ctxTpl(tpl: TemplateStringsArray, ...values: unknown[]) {
542
- ctx[method](tpl, ...values);
543
- }
544
-
545
- try {
546
- const before = Date.now();
547
- ctxTpl`Hello, ${123}!`;
548
- const after = Date.now();
549
- assertEquals(logs, [
550
- {
551
- category: ["foo"],
552
- level: method === "warn" ? "warning" : method,
553
- message: ["Hello, ", 123, "!"],
554
- rawMessage: templateLiteral`Hello, ${null}!`,
555
- timestamp: logs[0].timestamp,
556
- properties: { a: 1, b: 2 },
557
- },
558
- ]);
559
- assertGreaterOrEqual(logs[0].timestamp, before);
560
- assertLessOrEqual(logs[0].timestamp, after);
561
- } finally {
562
- logger.resetDescendants();
563
- }
564
- });
565
-
566
- test(`Logger.${method}() [lazy template]`, () => {
567
- const logger = LoggerImpl.getLogger("foo");
568
- const ctx = new LoggerCtx(logger, { a: 1, b: 2 });
569
-
570
- try {
571
- const logs: LogRecord[] = [];
572
- logger.sinks.push(logs.push.bind(logs));
573
- let before = Date.now();
574
- logger[method]((l) => l`Hello, ${123}!`);
575
- let after = Date.now();
576
- assertEquals(logs, [
577
- {
578
- category: ["foo"],
579
- level: method === "warn" ? "warning" : method,
580
- message: ["Hello, ", 123, "!"],
581
- rawMessage: templateLiteral`Hello, ${null}!`,
582
- timestamp: logs[0].timestamp,
583
- properties: {},
584
- },
585
- ]);
586
- assertGreaterOrEqual(logs[0].timestamp, before);
587
- assertLessOrEqual(logs[0].timestamp, after);
588
-
589
- logs.shift();
590
- before = Date.now();
591
- ctx[method]((l) => l`Hello, ${123}!`);
592
- after = Date.now();
593
- assertEquals(logs, [
594
- {
595
- category: ["foo"],
596
- level: method === "warn" ? "warning" : method,
597
- message: ["Hello, ", 123, "!"],
598
- rawMessage: templateLiteral`Hello, ${null}!`,
599
- timestamp: logs[0].timestamp,
600
- properties: { a: 1, b: 2 },
601
- },
602
- ]);
603
- assertGreaterOrEqual(logs[0].timestamp, before);
604
- assertLessOrEqual(logs[0].timestamp, after);
605
- } finally {
606
- logger.resetDescendants();
607
- }
608
- });
609
-
610
- test(`Logger.${method}() [eager]`, () => {
611
- const logger = LoggerImpl.getLogger("foo");
612
- const ctx = new LoggerCtx(logger, { a: 1, b: 2 });
613
-
614
- try {
615
- const logs: LogRecord[] = [];
616
- logger.sinks.push(logs.push.bind(logs));
617
- let before = Date.now();
618
- logger[method]("Hello, {foo}!", { foo: 123 });
619
- let after = Date.now();
620
- assertEquals(logs, [
621
- {
622
- category: ["foo"],
623
- level: method === "warn" ? "warning" : method,
624
- message: ["Hello, ", 123, "!"],
625
- rawMessage: "Hello, {foo}!",
626
- timestamp: logs[0].timestamp,
627
- properties: { foo: 123 },
628
- },
629
- ]);
630
- assertGreaterOrEqual(logs[0].timestamp, before);
631
- assertLessOrEqual(logs[0].timestamp, after);
632
-
633
- logs.shift();
634
- logger[method]("Hello, world!");
635
- assertEquals(logs, [
636
- {
637
- category: ["foo"],
638
- level: method === "warn" ? "warning" : method,
639
- message: ["Hello, world!"],
640
- rawMessage: "Hello, world!",
641
- timestamp: logs[0].timestamp,
642
- properties: {},
643
- },
644
- ]);
645
-
646
- logs.shift();
647
- before = Date.now();
648
- ctx[method]("Hello, {a} {b} {c}!", { c: 3 });
649
- after = Date.now();
650
- assertEquals(logs, [
651
- {
652
- category: ["foo"],
653
- level: method === "warn" ? "warning" : method,
654
- message: ["Hello, ", 1, " ", 2, " ", 3, "!"],
655
- rawMessage: "Hello, {a} {b} {c}!",
656
- timestamp: logs[0].timestamp,
657
- properties: { a: 1, b: 2, c: 3 },
658
- },
659
- ]);
660
-
661
- logs.shift();
662
- ctx[method]("Hello, world!");
663
- assertEquals(logs, [
664
- {
665
- category: ["foo"],
666
- level: method === "warn" ? "warning" : method,
667
- message: ["Hello, world!"],
668
- rawMessage: "Hello, world!",
669
- timestamp: logs[0].timestamp,
670
- properties: { a: 1, b: 2 },
671
- },
672
- ]);
673
- } finally {
674
- logger.resetDescendants();
675
- }
676
- });
677
-
678
- test(`Logger.${method}() [lazy]`, () => {
679
- const logger = LoggerImpl.getLogger("foo");
680
- const ctx = new LoggerCtx(logger, { a: 1, b: 2 });
681
-
682
- try {
683
- const logs: LogRecord[] = [];
684
- logger.sinks.push(logs.push.bind(logs));
685
- let before = Date.now();
686
- logger[method]("Hello, {foo}!", () => {
687
- return { foo: 123 };
688
- });
689
- let after = Date.now();
690
- assertEquals(logs, [
691
- {
692
- category: ["foo"],
693
- level: method === "warn" ? "warning" : method,
694
- message: ["Hello, ", 123, "!"],
695
- rawMessage: "Hello, {foo}!",
696
- timestamp: logs[0].timestamp,
697
- properties: { foo: 123 },
698
- },
699
- ]);
700
- assertGreaterOrEqual(logs[0].timestamp, before);
701
- assertLessOrEqual(logs[0].timestamp, after);
702
-
703
- logs.shift();
704
- before = Date.now();
705
- ctx[method]("Hello, {a} {b} {c}!", () => {
706
- return { c: 3 };
707
- });
708
- after = Date.now();
709
- assertEquals(logs, [
710
- {
711
- category: ["foo"],
712
- level: method === "warn" ? "warning" : method,
713
- message: ["Hello, ", 1, " ", 2, " ", 3, "!"],
714
- rawMessage: "Hello, {a} {b} {c}!",
715
- timestamp: logs[0].timestamp,
716
- properties: { a: 1, b: 2, c: 3 },
717
- },
718
- ]);
719
- assertGreaterOrEqual(logs[0].timestamp, before);
720
- assertLessOrEqual(logs[0].timestamp, after);
721
- } finally {
722
- logger.resetDescendants();
723
- }
724
- });
725
-
726
- test(`Logger.${method}() [with no message]`, () => {
727
- const logger = LoggerImpl.getLogger("foo");
728
-
729
- try {
730
- const logs: LogRecord[] = [];
731
- logger.sinks.push(logs.push.bind(logs));
732
- const before = Date.now();
733
- logger[method]({ foo: 123, bar: 456 });
734
- const after = Date.now();
735
- assertEquals(logs, [
736
- {
737
- category: ["foo"],
738
- level: method === "warn" ? "warning" : method,
739
- message: ["", { foo: 123, bar: 456 }, ""],
740
- rawMessage: "{*}",
741
- timestamp: logs[0].timestamp,
742
- properties: { foo: 123, bar: 456 },
743
- },
744
- ]);
745
- assertGreaterOrEqual(logs[0].timestamp, before);
746
- assertLessOrEqual(logs[0].timestamp, after);
747
- } finally {
748
- logger.resetDescendants();
749
- }
750
- });
751
- }
752
-
753
- test("parseMessageTemplate()", () => {
754
- assertEquals(parseMessageTemplate("Hello, world!", {}), ["Hello, world!"]);
755
- assertEquals(
756
- parseMessageTemplate("Hello, world!", { foo: 123 }),
757
- ["Hello, world!"],
758
- );
759
- assertEquals(
760
- parseMessageTemplate("Hello, {{world}}!", { foo: 123 }),
761
- ["Hello, {world}!"],
762
- );
763
- assertEquals(
764
- parseMessageTemplate("Hello, {foo}!", { foo: 123 }),
765
- ["Hello, ", 123, "!"],
766
- );
767
- assertEquals(
768
- parseMessageTemplate("Hello, { foo\t}!", { " foo\t": 123, foo: 456 }),
769
- ["Hello, ", 123, "!"],
770
- );
771
- assertEquals(
772
- parseMessageTemplate("Hello, { foo\t}!", { foo: 456 }),
773
- ["Hello, ", 456, "!"],
774
- );
775
- assertEquals(
776
- parseMessageTemplate("Hello, { foo\t}!", { " foo": 456 }),
777
- ["Hello, ", undefined, "!"],
778
- );
779
- assertEquals(
780
- parseMessageTemplate("Hello, {{foo}}!", { foo: 123 }),
781
- ["Hello, {foo}!"],
782
- );
783
- assertEquals(
784
- parseMessageTemplate("Hello, {bar}!", { foo: 123 }),
785
- ["Hello, ", undefined, "!"],
786
- );
787
- assertEquals(
788
- parseMessageTemplate("Hello, {bar}!", { foo: 123, bar: 456 }),
789
- ["Hello, ", 456, "!"],
790
- );
791
- assertEquals(
792
- parseMessageTemplate("Hello, {foo}, {bar}!", { foo: 123, bar: 456 }),
793
- ["Hello, ", 123, ", ", 456, "!"],
794
- );
795
- assertEquals(
796
- parseMessageTemplate("Hello, {foo}, {bar}", { foo: 123, bar: 456 }),
797
- ["Hello, ", 123, ", ", 456, ""],
798
- );
799
- assertEquals(
800
- parseMessageTemplate("Hello, {*}", { foo: 123, bar: 456 }),
801
- ["Hello, ", { foo: 123, bar: 456 }, ""],
802
- );
803
- assertEquals(
804
- parseMessageTemplate("Hello, { *\t}", { foo: 123, bar: 456 }),
805
- ["Hello, ", { foo: 123, bar: 456 }, ""],
806
- );
807
- assertEquals(
808
- parseMessageTemplate("Hello, {*}", { foo: 123, bar: 456, "*": 789 }),
809
- ["Hello, ", 789, ""],
810
- );
811
- assertEquals(
812
- parseMessageTemplate("Hello, { *\t}", { foo: 123, bar: 456, " *\t": 789 }),
813
- ["Hello, ", 789, ""],
814
- );
815
- assertEquals(
816
- parseMessageTemplate("Hello, { *\t}", { foo: 123, bar: 456, "*": 789 }),
817
- ["Hello, ", 789, ""],
818
- );
819
- assertEquals(
820
- parseMessageTemplate("Hello, {{world!", { foo: 123 }),
821
- ["Hello, {world!"],
822
- );
823
- });
824
-
825
- test("renderMessage()", () => {
826
- function rm(tpl: TemplateStringsArray, ...values: unknown[]) {
827
- return renderMessage(tpl, values);
828
- }
829
- assertEquals(rm`Hello, world!`, ["Hello, world!"]);
830
- assertEquals(rm`Hello, ${123}!`, ["Hello, ", 123, "!"]);
831
- assertEquals(rm`Hello, ${123}, ${456}!`, ["Hello, ", 123, ", ", 456, "!"]);
832
- assertEquals(rm`Hello, ${123}, ${456}`, ["Hello, ", 123, ", ", 456, ""]);
833
- });
834
-
835
- test("LogMethod", () => {
836
- // The below test ensures that the LogMethod type is correctly inferred,
837
- // which is checked at compile time:
838
- const logger = LoggerImpl.getLogger("foo");
839
- let _method: LogMethod = logger.trace;
840
- _method = logger.debug;
841
- _method = logger.info;
842
- _method = logger.warn;
843
- _method = logger.warning;
844
- _method = logger.error;
845
- _method = logger.fatal;
846
- const ctx = logger.with({});
847
- _method = ctx.trace;
848
- _method = ctx.debug;
849
- _method = ctx.info;
850
- _method = ctx.warn;
851
- _method = ctx.warning;
852
- _method = ctx.error;
853
- _method = ctx.fatal;
854
- });
855
-
856
- test("Logger.emit() with custom timestamp", () => {
857
- const logger = getLogger(["test", "emit"]);
858
- const records: LogRecord[] = [];
859
- const sink: Sink = (record) => records.push(record);
860
-
861
- try {
862
- (logger as LoggerImpl).sinks.push(sink);
863
-
864
- const customTimestamp = Date.now() - 60000; // 1 minute ago
865
- logger.emit({
866
- timestamp: customTimestamp,
867
- level: "info",
868
- message: ["Custom message with", "value"],
869
- rawMessage: "Custom message with {value}",
870
- properties: { value: "test", source: "external" },
871
- });
872
-
873
- assertEquals(records.length, 1);
874
- assertEquals(records[0].category, ["test", "emit"]);
875
- assertEquals(records[0].level, "info");
876
- assertEquals(records[0].timestamp, customTimestamp);
877
- assertEquals(records[0].message, ["Custom message with", "value"]);
878
- assertEquals(records[0].rawMessage, "Custom message with {value}");
879
- assertEquals(records[0].properties, { value: "test", source: "external" });
880
- } finally {
881
- (logger as LoggerImpl).reset();
882
- }
883
- });
884
-
885
- test("Logger.emit() preserves all fields", () => {
886
- const logger = getLogger("emit-test");
887
- const records: LogRecord[] = [];
888
- const sink: Sink = (record) => records.push(record);
889
-
890
- try {
891
- (logger as LoggerImpl).sinks.push(sink);
892
-
893
- const testTimestamp = 1672531200000; // Fixed timestamp
894
- const testMessage = ["Log from", "Kafka", "at", "partition 0"];
895
- const testRawMessage =
896
- "Log from {source} at {location} partition {partition}";
897
- const testProperties = {
898
- partition: 0,
899
- offset: 12345,
900
- topic: "test-topic",
901
- source: "kafka",
902
- };
903
-
904
- logger.emit({
905
- timestamp: testTimestamp,
906
- level: "debug",
907
- message: testMessage,
908
- rawMessage: testRawMessage,
909
- properties: testProperties,
910
- });
911
-
912
- assertEquals(records.length, 1);
913
- const record = records[0];
914
- assertEquals(record.category, ["emit-test"]);
915
- assertEquals(record.level, "debug");
916
- assertEquals(record.timestamp, testTimestamp);
917
- assertEquals(record.message, testMessage);
918
- assertEquals(record.rawMessage, testRawMessage);
919
- assertEquals(record.properties, testProperties);
920
- } finally {
921
- (logger as LoggerImpl).reset();
922
- }
923
- });
924
-
925
- test("LoggerCtx.emit() merges contextual properties", () => {
926
- const logger = getLogger("ctx-emit");
927
- const ctx = logger.with({ requestId: "req-123", userId: "user-456" });
928
- const records: LogRecord[] = [];
929
- const sink: Sink = (record) => records.push(record);
930
-
931
- try {
932
- (logger as LoggerImpl).sinks.push(sink);
933
-
934
- ctx.emit({
935
- timestamp: Date.now(),
936
- level: "warning",
937
- message: ["External warning from", "system"],
938
- rawMessage: "External warning from {system}",
939
- properties: { system: "external", priority: "high" },
940
- });
941
-
942
- assertEquals(records.length, 1);
943
- const record = records[0];
944
- assertEquals(record.category, ["ctx-emit"]);
945
- assertEquals(record.level, "warning");
946
- assertEquals(record.properties, {
947
- system: "external",
948
- priority: "high",
949
- requestId: "req-123",
950
- userId: "user-456",
951
- });
952
- } finally {
953
- (logger as LoggerImpl).reset();
954
- }
955
- });
956
-
957
- test("LoggerCtx.emit() record properties override context properties", () => {
958
- const logger = getLogger("override-test");
959
- const ctx = logger.with({ source: "context", shared: "from-context" });
960
- const records: LogRecord[] = [];
961
- const sink: Sink = (record) => records.push(record);
962
-
963
- try {
964
- (logger as LoggerImpl).sinks.push(sink);
965
-
966
- ctx.emit({
967
- timestamp: Date.now(),
968
- level: "error",
969
- message: ["Override test"],
970
- rawMessage: "Override test",
971
- properties: { source: "record", priority: "critical" },
972
- });
973
-
974
- assertEquals(records.length, 1);
975
- const record = records[0];
976
- assertEquals(record.properties, {
977
- source: "record", // record properties override context
978
- shared: "from-context", // context properties are preserved
979
- priority: "critical",
980
- });
981
- } finally {
982
- (logger as LoggerImpl).reset();
983
- }
984
- });
985
-
986
- test("Logger.emit() respects filters", () => {
987
- const logger = getLogger("filtered-emit");
988
- const records: LogRecord[] = [];
989
- const sink: Sink = (record) => records.push(record);
990
-
991
- try {
992
- (logger as LoggerImpl).sinks.push(sink);
993
- (logger as LoggerImpl).filters.push((record) => record.level !== "debug");
994
-
995
- // This should be filtered out
996
- logger.emit({
997
- timestamp: Date.now(),
998
- level: "debug",
999
- message: ["Debug message"],
1000
- rawMessage: "Debug message",
1001
- properties: {},
1002
- });
1003
-
1004
- // This should pass through
1005
- logger.emit({
1006
- timestamp: Date.now(),
1007
- level: "info",
1008
- message: ["Info message"],
1009
- rawMessage: "Info message",
1010
- properties: {},
1011
- });
1012
-
1013
- assertEquals(records.length, 1);
1014
- assertEquals(records[0].level, "info");
1015
- } finally {
1016
- (logger as LoggerImpl).reset();
1017
- }
1018
- });
1019
-
1020
- test("Logger.emit() respects log level threshold", () => {
1021
- const logger = getLogger("level-emit");
1022
- const records: LogRecord[] = [];
1023
- const sink: Sink = (record) => records.push(record);
1024
-
1025
- try {
1026
- (logger as LoggerImpl).sinks.push(sink);
1027
- (logger as LoggerImpl).lowestLevel = "warning";
1028
-
1029
- // This should be filtered out (below threshold)
1030
- logger.emit({
1031
- timestamp: Date.now(),
1032
- level: "info",
1033
- message: ["Info message"],
1034
- rawMessage: "Info message",
1035
- properties: {},
1036
- });
1037
-
1038
- // This should pass through (at threshold)
1039
- logger.emit({
1040
- timestamp: Date.now(),
1041
- level: "warning",
1042
- message: ["Warning message"],
1043
- rawMessage: "Warning message",
1044
- properties: {},
1045
- });
1046
-
1047
- assertEquals(records.length, 1);
1048
- assertEquals(records[0].level, "warning");
1049
- } finally {
1050
- (logger as LoggerImpl).reset();
1051
- }
1052
- });