@logtape/logtape 1.2.2 → 1.2.4

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,1508 +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
- assertEquals(
824
- parseMessageTemplate("Hello, {user.name}!", {
825
- user: { name: "foo", email: "foo@example.com" },
826
- }),
827
- ["Hello, ", "foo", "!"],
828
- );
829
- assertEquals(
830
- parseMessageTemplate("Email: {user.email}", {
831
- user: { name: "foo", email: "foo@example.com" },
832
- }),
833
- ["Email: ", "foo@example.com", ""],
834
- );
835
- assertEquals(
836
- parseMessageTemplate("Tier: {order.customer.profile.tier}", {
837
- order: {
838
- customer: {
839
- profile: {
840
- tier: "premium",
841
- },
842
- },
843
- },
844
- }),
845
- ["Tier: ", "premium", ""],
846
- );
847
- assertEquals(
848
- parseMessageTemplate("Missing: {user.email}", {
849
- user: { name: "foo" },
850
- }),
851
- ["Missing: ", undefined, ""],
852
- );
853
- assertEquals(
854
- parseMessageTemplate("Deep missing: {user.profile.email}", {
855
- user: { name: "foo" },
856
- }),
857
- ["Deep missing: ", undefined, ""],
858
- );
859
- assertEquals(
860
- parseMessageTemplate("First user: {users[0]}", {
861
- users: ["foo", "bar", "baz"],
862
- }),
863
- ["First user: ", "foo", ""],
864
- );
865
- assertEquals(
866
- parseMessageTemplate("Third user: {users[2]}", {
867
- users: ["foo", "bar", "baz"],
868
- }),
869
- ["Third user: ", "baz", ""],
870
- );
871
- assertEquals(
872
- parseMessageTemplate("Admin: {users[0].name}", {
873
- users: [
874
- { name: "foo", role: "admin" },
875
- { name: "bar", role: "user" },
876
- ],
877
- }),
878
- ["Admin: ", "foo", ""],
879
- );
880
- assertEquals(
881
- parseMessageTemplate("User role: {users[1].role}", {
882
- users: [
883
- { name: "foo", role: "admin" },
884
- { name: "bar", role: "user" },
885
- ],
886
- }),
887
- ["User role: ", "user", ""],
888
- );
889
- assertEquals(
890
- parseMessageTemplate("Beyond: {users[5]}", {
891
- users: ["foo", "bar"],
892
- }),
893
- ["Beyond: ", undefined, ""],
894
- );
895
- assertEquals(
896
- parseMessageTemplate("Invalid: {user[0]}", {
897
- user: "foo",
898
- }),
899
- ["Invalid: ", undefined, ""],
900
- );
901
- assertEquals(
902
- parseMessageTemplate('Full name: {user["full-name"]}', {
903
- user: { "full-name": "foo bar", "user-id": 123 },
904
- }),
905
- ["Full name: ", "foo bar", ""],
906
- );
907
- assertEquals(
908
- parseMessageTemplate('User ID: {user["user-id"]}', {
909
- user: { "full-name": "foo bar", "user-id": 123 },
910
- }),
911
- ["User ID: ", 123, ""],
912
- );
913
- assertEquals(
914
- parseMessageTemplate("Name: {user['full-name']}", {
915
- user: { "full-name": "foo bar", "nick-name": "fb" },
916
- }),
917
- ["Name: ", "foo bar", ""],
918
- );
919
- assertEquals(
920
- parseMessageTemplate('Nick: {user["nick-name"]}', {
921
- user: { "full-name": "foo bar", "nick-name": "fb" },
922
- }),
923
- ["Nick: ", "fb", ""],
924
- );
925
- assertEquals(
926
- parseMessageTemplate('Custom: {data["custom field"]}', {
927
- data: { "custom field": "value" },
928
- }),
929
- ["Custom: ", "value", ""],
930
- );
931
- assertEquals(
932
- parseMessageTemplate('First user: {users[0]["full-name"]}', {
933
- users: [{ "full-name": "foo bar" }, { "full-name": "bar baz" }],
934
- }),
935
- ["First user: ", "foo bar", ""],
936
- );
937
- assertEquals(
938
- parseMessageTemplate("Name: {user?.name}", {
939
- user: { name: "foo" },
940
- }),
941
- ["Name: ", "foo", ""],
942
- );
943
- assertEquals(
944
- parseMessageTemplate("Email: {user?.profile?.email}", {
945
- user: { name: "foo" },
946
- }),
947
- ["Email: ", undefined, ""],
948
- );
949
- assertEquals(
950
- parseMessageTemplate("Item: {data?.items?.[0]?.name}", {
951
- data: null,
952
- }),
953
- ["Item: ", undefined, ""],
954
- );
955
- assertEquals(
956
- parseMessageTemplate("Name: {user?.name}", {
957
- user: null,
958
- }),
959
- ["Name: ", undefined, ""],
960
- );
961
- assertEquals(
962
- parseMessageTemplate(
963
- 'Email: {users[0]?.profile?.["contact-info"]?.email}',
964
- {
965
- users: [
966
- {
967
- profile: {
968
- "contact-info": {
969
- email: "foo@example.com",
970
- },
971
- },
972
- },
973
- ],
974
- },
975
- ),
976
- ["Email: ", "foo@example.com", ""],
977
- );
978
- assertEquals(
979
- parseMessageTemplate("Hello, {user}!", {
980
- user: "foo",
981
- count: 42,
982
- }),
983
- ["Hello, ", "foo", "!"],
984
- );
985
- assertEquals(
986
- parseMessageTemplate("Dot property: {user.name}", {
987
- "user.name": "foo",
988
- "user.email": "foo@example.com",
989
- }),
990
- ["Dot property: ", "foo", ""],
991
- );
992
- assertEquals(
993
- parseMessageTemplate("All: {*}", {
994
- user: { name: "foo" },
995
- count: 42,
996
- }),
997
- ["All: ", { user: { name: "foo" }, count: 42 }, ""],
998
- );
999
- assertEquals(
1000
- parseMessageTemplate('Malformed: {user["name}', {
1001
- user: { name: "foo" },
1002
- }),
1003
- ["Malformed: ", undefined, ""],
1004
- );
1005
- assertEquals(
1006
- parseMessageTemplate("Empty: {user[]}", {
1007
- user: { name: "foo" },
1008
- }),
1009
- ["Empty: ", undefined, ""],
1010
- );
1011
- assertEquals(
1012
- parseMessageTemplate("Invalid: {users[abc]}", {
1013
- users: ["foo", "bar"],
1014
- }),
1015
- ["Invalid: ", undefined, ""],
1016
- );
1017
- assertEquals(
1018
- parseMessageTemplate("Protected: {foo.constructor}", {
1019
- foo: { bar: 123 },
1020
- }),
1021
- ["Protected: ", undefined, ""],
1022
- );
1023
- assertEquals(
1024
- parseMessageTemplate("Protected: {foo.prototype}", {
1025
- foo: { bar: 123 },
1026
- }),
1027
- ["Protected: ", undefined, ""],
1028
- );
1029
- assertEquals(
1030
- parseMessageTemplate("Protected: {foo.__proto__}", {
1031
- foo: { bar: 123 },
1032
- }),
1033
- ["Protected: ", undefined, ""],
1034
- );
1035
-
1036
- // Boundary conditions
1037
- assertEquals(parseMessageTemplate("", {}), [""]);
1038
- assertEquals(
1039
- parseMessageTemplate("no placeholders", {}),
1040
- ["no placeholders"],
1041
- );
1042
- assertEquals(parseMessageTemplate("{value}", { value: 1 }), ["", 1, ""]);
1043
- assertEquals(
1044
- parseMessageTemplate("A {x}{y} B", { x: 1, y: 2 }),
1045
- ["A ", 1, "", 2, " B"],
1046
- );
1047
- assertEquals(
1048
- parseMessageTemplate("Deep: {a.b.c.d.e}", {
1049
- a: { b: { c: { d: { e: 5 } } } },
1050
- }),
1051
- ["Deep: ", 5, ""],
1052
- );
1053
- assertEquals(
1054
- parseMessageTemplate("2D: {m[1][0]}", { m: [[0, 1], [2, 3]] }),
1055
- ["2D: ", 2, ""],
1056
- );
1057
-
1058
- // Parsing error cases
1059
- assertEquals(
1060
- parseMessageTemplate("Missing: {user", { user: 1 }),
1061
- ["Missing: {user"],
1062
- );
1063
- assertEquals(parseMessageTemplate("Extra: user}", {}), ["Extra: user}"]);
1064
- assertEquals(
1065
- parseMessageTemplate("Bad: {user.}", { user: { name: "x" } }),
1066
- ["Bad: ", undefined, ""],
1067
- );
1068
- assertEquals(
1069
- parseMessageTemplate("Bad: {.user}", { user: { name: "x" } }),
1070
- ["Bad: ", undefined, ""],
1071
- );
1072
- assertEquals(
1073
- parseMessageTemplate("Bad: {user..name}", { user: { name: "x" } }),
1074
- ["Bad: ", undefined, ""],
1075
- );
1076
- assertEquals(
1077
- parseMessageTemplate("Bad idx: {arr[-1]}", { arr: [1] }),
1078
- ["Bad idx: ", undefined, ""],
1079
- );
1080
- assertEquals(
1081
- parseMessageTemplate("Float idx: {arr[1.5]}", { arr: [1, 2] }),
1082
- ["Float idx: ", undefined, ""],
1083
- );
1084
- assertEquals(
1085
- parseMessageTemplate('Bad quote: {user["na\\"}', {
1086
- user: { 'na"': "v" },
1087
- }),
1088
- ["Bad quote: ", undefined, ""],
1089
- );
1090
- assertEquals(parseMessageTemplate("Empty: {}", { a: 1 }), [
1091
- "Empty: ",
1092
- undefined,
1093
- "",
1094
- ]);
1095
-
1096
- // Type conversion - ensure values are not stringified
1097
- assertEquals(parseMessageTemplate("Num: {n}", { n: 0 }), ["Num: ", 0, ""]);
1098
- assertEquals(parseMessageTemplate("Bool: {b}", { b: true }), [
1099
- "Bool: ",
1100
- true,
1101
- "",
1102
- ]);
1103
- assertEquals(parseMessageTemplate("Null: {x}", { x: null }), [
1104
- "Null: ",
1105
- null,
1106
- "",
1107
- ]);
1108
- assertEquals(parseMessageTemplate("Undef: {x}", {}), [
1109
- "Undef: ",
1110
- undefined,
1111
- "",
1112
- ]);
1113
- const testSymbol = Symbol("test");
1114
- assertEquals(parseMessageTemplate("Sym: {s}", { s: testSymbol }), [
1115
- "Sym: ",
1116
- testSymbol,
1117
- "",
1118
- ]);
1119
- assertEquals(parseMessageTemplate("BigInt: {b}", { b: 10n }), [
1120
- "BigInt: ",
1121
- 10n,
1122
- "",
1123
- ]);
1124
- const testFn = () => {};
1125
- assertEquals(parseMessageTemplate("Fn: {fn}", { fn: testFn }), [
1126
- "Fn: ",
1127
- testFn,
1128
- "",
1129
- ]);
1130
- const testDate = new Date(0);
1131
- assertEquals(parseMessageTemplate("Date: {d}", { d: testDate }), [
1132
- "Date: ",
1133
- testDate,
1134
- "",
1135
- ]);
1136
- const testRegex = /x/;
1137
- assertEquals(parseMessageTemplate("RegExp: {r}", { r: testRegex }), [
1138
- "RegExp: ",
1139
- testRegex,
1140
- "",
1141
- ]);
1142
- assertEquals(
1143
- parseMessageTemplate("Nested arr: {a[1][0]}", { a: [[0], [1, 2]] }),
1144
- ["Nested arr: ", 1, ""],
1145
- );
1146
-
1147
- // Complex patterns
1148
- assertEquals(
1149
- parseMessageTemplate("Hi {first} {last}", { first: "A", last: "B" }),
1150
- ["Hi ", "A", " ", "B", ""],
1151
- );
1152
- assertEquals(parseMessageTemplate("{a}{b}{c}", { a: 1, b: 2, c: 3 }), [
1153
- "",
1154
- 1,
1155
- "",
1156
- 2,
1157
- "",
1158
- 3,
1159
- "",
1160
- ]);
1161
- assertEquals(
1162
- parseMessageTemplate("Mix: {users?.[0].profile['full-name']}", {
1163
- users: [{ profile: { "full-name": "X" } }],
1164
- }),
1165
- ["Mix: ", "X", ""],
1166
- );
1167
- assertEquals(
1168
- parseMessageTemplate("Len: {arr.length}", { arr: [1, 2, 3] }),
1169
- ["Len: ", 3, ""],
1170
- );
1171
- assertEquals(
1172
- parseMessageTemplate("All: {*}, id={id}", { id: 1, a: 2 }),
1173
- ["All: ", { id: 1, a: 2 }, ", id=", 1, ""],
1174
- );
1175
-
1176
- // Security - block dangerous props at any depth
1177
- assertEquals(
1178
- parseMessageTemplate("Blocked: {user.profile.constructor}", {
1179
- user: { profile: {} },
1180
- }),
1181
- ["Blocked: ", undefined, ""],
1182
- );
1183
- assertEquals(
1184
- parseMessageTemplate('Blocked: {user["__proto__"]}', { user: {} }),
1185
- ["Blocked: ", undefined, ""],
1186
- );
1187
- assertEquals(
1188
- parseMessageTemplate('Blocked: {user.profile["constructor"]}', {
1189
- user: { profile: {} },
1190
- }),
1191
- ["Blocked: ", undefined, ""],
1192
- );
1193
- assertEquals(
1194
- parseMessageTemplate('Blocked: {obj["prototype"]}', { obj: {} }),
1195
- ["Blocked: ", undefined, ""],
1196
- );
1197
-
1198
- // Optional chaining variants
1199
- assertEquals(
1200
- parseMessageTemplate("Root opt: {arr?.[0]}", { arr: ["x"] }),
1201
- ["Root opt: ", "x", ""],
1202
- );
1203
- assertEquals(
1204
- parseMessageTemplate("Opt mid: {a?.b.c}", { a: null }),
1205
- ["Opt mid: ", undefined, ""],
1206
- );
1207
- assertEquals(
1208
- parseMessageTemplate("Opt end: {a.b?.c}", { a: { b: null } }),
1209
- ["Opt end: ", undefined, ""],
1210
- );
1211
- assertEquals(
1212
- parseMessageTemplate('Opt quoted: {obj?.["k-v"]}', { obj: { "k-v": 1 } }),
1213
- ["Opt quoted: ", 1, ""],
1214
- );
1215
- assertEquals(
1216
- parseMessageTemplate("Opt after idx: {list[0]?.name}", {
1217
- list: [{ name: "x" }],
1218
- }),
1219
- ["Opt after idx: ", "x", ""],
1220
- );
1221
-
1222
- // Unicode and special characters
1223
- assertEquals(
1224
- parseMessageTemplate('Emoji: {data["😀"]}', { data: { "😀": 1 } }),
1225
- ["Emoji: ", 1, ""],
1226
- );
1227
- assertEquals(
1228
- parseMessageTemplate('Astral: {data["𝟘𝟙"]}', { data: { "𝟘𝟙": "ok" } }),
1229
- ["Astral: ", "ok", ""],
1230
- );
1231
- assertEquals(
1232
- parseMessageTemplate(String.raw`Quotes: {data["quo\"te"]}`, {
1233
- data: { 'quo"te': 1 },
1234
- }),
1235
- ["Quotes: ", 1, ""],
1236
- );
1237
- assertEquals(
1238
- parseMessageTemplate(String.raw`SQuotes: {data['sin\'gle']}`, {
1239
- data: { "sin'gle": 2 },
1240
- }),
1241
- ["SQuotes: ", 2, ""],
1242
- );
1243
- assertEquals(
1244
- parseMessageTemplate(String.raw`Backslash: {data["back\\slash"]}`, {
1245
- data: { "back\\slash": 3 },
1246
- }),
1247
- ["Backslash: ", 3, ""],
1248
- );
1249
- assertEquals(
1250
- parseMessageTemplate(String.raw`Newline: {data["line\nbreak"]}`, {
1251
- data: { "line\nbreak": 4 },
1252
- }),
1253
- ["Newline: ", 4, ""],
1254
- );
1255
- assertEquals(
1256
- parseMessageTemplate(String.raw`Tab: {data["tab\tseparated"]}`, {
1257
- data: { "tab\tseparated": 5 },
1258
- }),
1259
- ["Tab: ", 5, ""],
1260
- );
1261
- assertEquals(
1262
- parseMessageTemplate(String.raw`Multiple: {data["a\nb\tc"]}`, {
1263
- data: { "a\nb\tc": 6 },
1264
- }),
1265
- ["Multiple: ", 6, ""],
1266
- );
1267
- assertEquals(
1268
- parseMessageTemplate(String.raw`Unicode: {data["smile\u263A"]}`, {
1269
- data: { "smile☺": 7 },
1270
- }),
1271
- ["Unicode: ", 7, ""],
1272
- );
1273
- assertEquals(
1274
- parseMessageTemplate('Dot in key: {data["a.b c"]}', {
1275
- data: { "a.b c": 1 },
1276
- }),
1277
- ["Dot in key: ", 1, ""],
1278
- );
1279
- });
1280
-
1281
- test("renderMessage()", () => {
1282
- function rm(tpl: TemplateStringsArray, ...values: unknown[]) {
1283
- return renderMessage(tpl, values);
1284
- }
1285
- assertEquals(rm`Hello, world!`, ["Hello, world!"]);
1286
- assertEquals(rm`Hello, ${123}!`, ["Hello, ", 123, "!"]);
1287
- assertEquals(rm`Hello, ${123}, ${456}!`, ["Hello, ", 123, ", ", 456, "!"]);
1288
- assertEquals(rm`Hello, ${123}, ${456}`, ["Hello, ", 123, ", ", 456, ""]);
1289
- });
1290
-
1291
- test("LogMethod", () => {
1292
- // The below test ensures that the LogMethod type is correctly inferred,
1293
- // which is checked at compile time:
1294
- const logger = LoggerImpl.getLogger("foo");
1295
- let _method: LogMethod = logger.trace;
1296
- _method = logger.debug;
1297
- _method = logger.info;
1298
- _method = logger.warn;
1299
- _method = logger.warning;
1300
- _method = logger.error;
1301
- _method = logger.fatal;
1302
- const ctx = logger.with({});
1303
- _method = ctx.trace;
1304
- _method = ctx.debug;
1305
- _method = ctx.info;
1306
- _method = ctx.warn;
1307
- _method = ctx.warning;
1308
- _method = ctx.error;
1309
- _method = ctx.fatal;
1310
- });
1311
-
1312
- test("Logger.emit() with custom timestamp", () => {
1313
- const logger = getLogger(["test", "emit"]);
1314
- const records: LogRecord[] = [];
1315
- const sink: Sink = (record) => records.push(record);
1316
-
1317
- try {
1318
- (logger as LoggerImpl).sinks.push(sink);
1319
-
1320
- const customTimestamp = Date.now() - 60000; // 1 minute ago
1321
- logger.emit({
1322
- timestamp: customTimestamp,
1323
- level: "info",
1324
- message: ["Custom message with", "value"],
1325
- rawMessage: "Custom message with {value}",
1326
- properties: { value: "test", source: "external" },
1327
- });
1328
-
1329
- assertEquals(records.length, 1);
1330
- assertEquals(records[0].category, ["test", "emit"]);
1331
- assertEquals(records[0].level, "info");
1332
- assertEquals(records[0].timestamp, customTimestamp);
1333
- assertEquals(records[0].message, ["Custom message with", "value"]);
1334
- assertEquals(records[0].rawMessage, "Custom message with {value}");
1335
- assertEquals(records[0].properties, { value: "test", source: "external" });
1336
- } finally {
1337
- (logger as LoggerImpl).reset();
1338
- }
1339
- });
1340
-
1341
- test("Logger.emit() preserves all fields", () => {
1342
- const logger = getLogger("emit-test");
1343
- const records: LogRecord[] = [];
1344
- const sink: Sink = (record) => records.push(record);
1345
-
1346
- try {
1347
- (logger as LoggerImpl).sinks.push(sink);
1348
-
1349
- const testTimestamp = 1672531200000; // Fixed timestamp
1350
- const testMessage = ["Log from", "Kafka", "at", "partition 0"];
1351
- const testRawMessage =
1352
- "Log from {source} at {location} partition {partition}";
1353
- const testProperties = {
1354
- partition: 0,
1355
- offset: 12345,
1356
- topic: "test-topic",
1357
- source: "kafka",
1358
- };
1359
-
1360
- logger.emit({
1361
- timestamp: testTimestamp,
1362
- level: "debug",
1363
- message: testMessage,
1364
- rawMessage: testRawMessage,
1365
- properties: testProperties,
1366
- });
1367
-
1368
- assertEquals(records.length, 1);
1369
- const record = records[0];
1370
- assertEquals(record.category, ["emit-test"]);
1371
- assertEquals(record.level, "debug");
1372
- assertEquals(record.timestamp, testTimestamp);
1373
- assertEquals(record.message, testMessage);
1374
- assertEquals(record.rawMessage, testRawMessage);
1375
- assertEquals(record.properties, testProperties);
1376
- } finally {
1377
- (logger as LoggerImpl).reset();
1378
- }
1379
- });
1380
-
1381
- test("LoggerCtx.emit() merges contextual properties", () => {
1382
- const logger = getLogger("ctx-emit");
1383
- const ctx = logger.with({ requestId: "req-123", userId: "user-456" });
1384
- const records: LogRecord[] = [];
1385
- const sink: Sink = (record) => records.push(record);
1386
-
1387
- try {
1388
- (logger as LoggerImpl).sinks.push(sink);
1389
-
1390
- ctx.emit({
1391
- timestamp: Date.now(),
1392
- level: "warning",
1393
- message: ["External warning from", "system"],
1394
- rawMessage: "External warning from {system}",
1395
- properties: { system: "external", priority: "high" },
1396
- });
1397
-
1398
- assertEquals(records.length, 1);
1399
- const record = records[0];
1400
- assertEquals(record.category, ["ctx-emit"]);
1401
- assertEquals(record.level, "warning");
1402
- assertEquals(record.properties, {
1403
- system: "external",
1404
- priority: "high",
1405
- requestId: "req-123",
1406
- userId: "user-456",
1407
- });
1408
- } finally {
1409
- (logger as LoggerImpl).reset();
1410
- }
1411
- });
1412
-
1413
- test("LoggerCtx.emit() record properties override context properties", () => {
1414
- const logger = getLogger("override-test");
1415
- const ctx = logger.with({ source: "context", shared: "from-context" });
1416
- const records: LogRecord[] = [];
1417
- const sink: Sink = (record) => records.push(record);
1418
-
1419
- try {
1420
- (logger as LoggerImpl).sinks.push(sink);
1421
-
1422
- ctx.emit({
1423
- timestamp: Date.now(),
1424
- level: "error",
1425
- message: ["Override test"],
1426
- rawMessage: "Override test",
1427
- properties: { source: "record", priority: "critical" },
1428
- });
1429
-
1430
- assertEquals(records.length, 1);
1431
- const record = records[0];
1432
- assertEquals(record.properties, {
1433
- source: "record", // record properties override context
1434
- shared: "from-context", // context properties are preserved
1435
- priority: "critical",
1436
- });
1437
- } finally {
1438
- (logger as LoggerImpl).reset();
1439
- }
1440
- });
1441
-
1442
- test("Logger.emit() respects filters", () => {
1443
- const logger = getLogger("filtered-emit");
1444
- const records: LogRecord[] = [];
1445
- const sink: Sink = (record) => records.push(record);
1446
-
1447
- try {
1448
- (logger as LoggerImpl).sinks.push(sink);
1449
- (logger as LoggerImpl).filters.push((record) => record.level !== "debug");
1450
-
1451
- // This should be filtered out
1452
- logger.emit({
1453
- timestamp: Date.now(),
1454
- level: "debug",
1455
- message: ["Debug message"],
1456
- rawMessage: "Debug message",
1457
- properties: {},
1458
- });
1459
-
1460
- // This should pass through
1461
- logger.emit({
1462
- timestamp: Date.now(),
1463
- level: "info",
1464
- message: ["Info message"],
1465
- rawMessage: "Info message",
1466
- properties: {},
1467
- });
1468
-
1469
- assertEquals(records.length, 1);
1470
- assertEquals(records[0].level, "info");
1471
- } finally {
1472
- (logger as LoggerImpl).reset();
1473
- }
1474
- });
1475
-
1476
- test("Logger.emit() respects log level threshold", () => {
1477
- const logger = getLogger("level-emit");
1478
- const records: LogRecord[] = [];
1479
- const sink: Sink = (record) => records.push(record);
1480
-
1481
- try {
1482
- (logger as LoggerImpl).sinks.push(sink);
1483
- (logger as LoggerImpl).lowestLevel = "warning";
1484
-
1485
- // This should be filtered out (below threshold)
1486
- logger.emit({
1487
- timestamp: Date.now(),
1488
- level: "info",
1489
- message: ["Info message"],
1490
- rawMessage: "Info message",
1491
- properties: {},
1492
- });
1493
-
1494
- // This should pass through (at threshold)
1495
- logger.emit({
1496
- timestamp: Date.now(),
1497
- level: "warning",
1498
- message: ["Warning message"],
1499
- rawMessage: "Warning message",
1500
- properties: {},
1501
- });
1502
-
1503
- assertEquals(records.length, 1);
1504
- assertEquals(records[0].level, "warning");
1505
- } finally {
1506
- (logger as LoggerImpl).reset();
1507
- }
1508
- });