@logtape/logtape 1.0.0-dev.240 → 1.0.0-dev.246

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/sink.test.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { suite } from "@alinea/suite";
2
- import { assert } from "@std/assert/assert";
3
2
  import { assertEquals } from "@std/assert/equals";
4
3
  import { assertInstanceOf } from "@std/assert/instance-of";
5
4
  import { assertThrows } from "@std/assert/throws";
@@ -68,254 +67,6 @@ test("getStreamSink()", async () => {
68
67
  );
69
68
  });
70
69
 
71
- test("getStreamSink() with nonBlocking - simple boolean", async () => {
72
- let buffer: string = "";
73
- const decoder = new TextDecoder();
74
- const sink = getStreamSink(
75
- new WritableStream({
76
- write(chunk: Uint8Array) {
77
- buffer += decoder.decode(chunk);
78
- return Promise.resolve();
79
- },
80
- }),
81
- { nonBlocking: true },
82
- );
83
-
84
- // Check that it returns AsyncDisposable
85
- assertInstanceOf(sink, Function);
86
- assert(Symbol.asyncDispose in sink);
87
-
88
- // Add records - they should not be written immediately
89
- sink(trace);
90
- sink(debug);
91
- assertEquals(buffer, ""); // Not written yet
92
-
93
- // Wait for flush interval (default 100ms)
94
- await delay(150);
95
- assertEquals(
96
- buffer,
97
- `2023-11-14 22:13:20.000 +00:00 [TRC] my-app·junk: Hello, 123 & 456!
98
- 2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
99
- `,
100
- );
101
-
102
- await sink[Symbol.asyncDispose]();
103
- });
104
-
105
- test("getStreamSink() with nonBlocking - custom buffer config", async () => {
106
- let buffer: string = "";
107
- const decoder = new TextDecoder();
108
- const sink = getStreamSink(
109
- new WritableStream({
110
- write(chunk: Uint8Array) {
111
- buffer += decoder.decode(chunk);
112
- return Promise.resolve();
113
- },
114
- }),
115
- {
116
- nonBlocking: {
117
- bufferSize: 2,
118
- flushInterval: 50,
119
- },
120
- },
121
- );
122
-
123
- // Add records up to buffer size
124
- sink(trace);
125
- assertEquals(buffer, ""); // Not flushed yet
126
-
127
- sink(debug); // This should trigger immediate flush (buffer size = 2)
128
- await delay(10); // Small delay for async flush
129
- assertEquals(
130
- buffer,
131
- `2023-11-14 22:13:20.000 +00:00 [TRC] my-app·junk: Hello, 123 & 456!
132
- 2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
133
- `,
134
- );
135
-
136
- // Add more records
137
- const prevLength = buffer.length;
138
- sink(info);
139
- assertEquals(buffer.length, prevLength); // Not flushed yet
140
-
141
- // Wait for flush interval
142
- await delay(60);
143
- assertEquals(
144
- buffer.substring(prevLength),
145
- `2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
146
- `,
147
- );
148
-
149
- await sink[Symbol.asyncDispose]();
150
- });
151
-
152
- test("getStreamSink() with nonBlocking - no operations after dispose", async () => {
153
- let buffer: string = "";
154
- const decoder = new TextDecoder();
155
- const sink = getStreamSink(
156
- new WritableStream({
157
- write(chunk: Uint8Array) {
158
- buffer += decoder.decode(chunk);
159
- return Promise.resolve();
160
- },
161
- }),
162
- { nonBlocking: true },
163
- );
164
-
165
- // Dispose immediately
166
- await sink[Symbol.asyncDispose]();
167
-
168
- // Try to add records after dispose
169
- sink(trace);
170
- sink(debug);
171
-
172
- // No records should be written
173
- assertEquals(buffer, "");
174
- });
175
-
176
- test("getStreamSink() with nonBlocking - error handling", async () => {
177
- const sink = getStreamSink(
178
- new WritableStream({
179
- write() {
180
- return Promise.reject(new Error("Write error"));
181
- },
182
- }),
183
- { nonBlocking: true },
184
- );
185
-
186
- // Should not throw when adding records
187
- sink(trace);
188
- sink(info);
189
- sink(error);
190
-
191
- // Wait for flush - errors should be silently ignored
192
- await delay(150);
193
-
194
- // Dispose - should not throw
195
- await sink[Symbol.asyncDispose]();
196
- });
197
-
198
- test("getStreamSink() with nonBlocking - flush on dispose", async () => {
199
- let buffer: string = "";
200
- const decoder = new TextDecoder();
201
- const sink = getStreamSink(
202
- new WritableStream({
203
- write(chunk: Uint8Array) {
204
- buffer += decoder.decode(chunk);
205
- return Promise.resolve();
206
- },
207
- }),
208
- {
209
- nonBlocking: {
210
- bufferSize: 100,
211
- flushInterval: 5000, // Very long interval
212
- },
213
- },
214
- );
215
-
216
- // Add records
217
- sink(trace);
218
- sink(debug);
219
- sink(info);
220
- assertEquals(buffer, ""); // Not flushed yet due to large buffer and long interval
221
-
222
- // Dispose should flush all remaining records
223
- await sink[Symbol.asyncDispose]();
224
- assertEquals(
225
- buffer,
226
- `2023-11-14 22:13:20.000 +00:00 [TRC] my-app·junk: Hello, 123 & 456!
227
- 2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
228
- 2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
229
- `,
230
- );
231
- });
232
-
233
- test("getStreamSink() with nonBlocking - buffer overflow protection", async () => {
234
- let buffer: string = "";
235
- const decoder = new TextDecoder();
236
- let recordsReceived = 0;
237
- const sink = getStreamSink(
238
- new WritableStream({
239
- write(chunk: Uint8Array) {
240
- const text = decoder.decode(chunk);
241
- buffer += text;
242
- // Count how many log records we actually receive
243
- recordsReceived += text.split("\n").filter((line) =>
244
- line.trim() !== ""
245
- ).length;
246
- return Promise.resolve();
247
- },
248
- }),
249
- {
250
- nonBlocking: {
251
- bufferSize: 3,
252
- flushInterval: 50, // Short interval to ensure flushes happen
253
- },
254
- },
255
- );
256
-
257
- // Add many more records than maxBufferSize (6) very rapidly
258
- // This should trigger multiple flushes and potentially overflow protection
259
- for (let i = 0; i < 20; i++) {
260
- sink(trace);
261
- }
262
-
263
- // Wait for all flushes to complete
264
- await delay(200);
265
-
266
- // Force final flush
267
- await sink[Symbol.asyncDispose]();
268
-
269
- // Due to overflow protection, we should receive fewer than 20 records
270
- // The exact number depends on timing, but some should be dropped
271
- assert(
272
- recordsReceived < 20,
273
- `Expected < 20 records due to potential overflow, got ${recordsReceived}`,
274
- );
275
- assert(recordsReceived > 0, "Expected some records to be logged");
276
- });
277
-
278
- test("getStreamSink() with nonBlocking - high volume non-blocking behavior", async () => {
279
- let buffer: string = "";
280
- const decoder = new TextDecoder();
281
- const sink = getStreamSink(
282
- new WritableStream({
283
- write(chunk: Uint8Array) {
284
- buffer += decoder.decode(chunk);
285
- return Promise.resolve();
286
- },
287
- }),
288
- {
289
- nonBlocking: {
290
- bufferSize: 3,
291
- flushInterval: 50,
292
- },
293
- },
294
- );
295
-
296
- // Simulate rapid logging - this should not block
297
- const startTime = performance.now();
298
- for (let i = 0; i < 100; i++) {
299
- sink(trace);
300
- }
301
- const endTime = performance.now();
302
-
303
- // Adding logs should be very fast (non-blocking)
304
- const duration = endTime - startTime;
305
- assert(
306
- duration < 100,
307
- `Adding 100 logs took ${duration}ms, should be much faster`,
308
- );
309
-
310
- // Wait for flushes to complete
311
- await delay(200);
312
-
313
- // Should have logged some records
314
- assert(buffer.length > 0, "Expected some records to be logged");
315
-
316
- await sink[Symbol.asyncDispose]();
317
- });
318
-
319
70
  test("getConsoleSink()", () => {
320
71
  // @ts-ignore: consolemock is not typed
321
72
  const mock: ConsoleMock = makeConsoleMock();
@@ -506,176 +257,6 @@ test("getConsoleSink()", () => {
506
257
  ]);
507
258
  });
508
259
 
509
- test("getConsoleSink() with nonBlocking - simple boolean", async () => {
510
- // @ts-ignore: consolemock is not typed
511
- const mock: ConsoleMock = makeConsoleMock();
512
- const sink = getConsoleSink({ console: mock, nonBlocking: true });
513
-
514
- // Check that it returns a Disposable
515
- assertInstanceOf(sink, Function);
516
- assert(Symbol.dispose in sink);
517
-
518
- // Add records - they should not be logged immediately
519
- sink(trace);
520
- sink(debug);
521
- assertEquals(mock.history().length, 0); // Not logged yet
522
-
523
- // Wait for flush interval (default 100ms)
524
- await delay(150);
525
- assertEquals(mock.history().length, 2); // Now they should be logged
526
-
527
- // Dispose the sink
528
- (sink as Sink & Disposable)[Symbol.dispose]();
529
- });
530
-
531
- test("getConsoleSink() with nonBlocking - custom buffer config", async () => {
532
- // @ts-ignore: consolemock is not typed
533
- const mock: ConsoleMock = makeConsoleMock();
534
- const sink = getConsoleSink({
535
- console: mock,
536
- nonBlocking: {
537
- bufferSize: 3,
538
- flushInterval: 50,
539
- },
540
- });
541
-
542
- // Add records up to buffer size
543
- sink(trace);
544
- sink(debug);
545
- assertEquals(mock.history().length, 0); // Not flushed yet
546
-
547
- sink(info); // This should trigger scheduled flush (buffer size = 3)
548
- await delay(10); // Wait for scheduled flush to execute
549
- assertEquals(mock.history().length, 3); // Flushed due to buffer size
550
-
551
- // Add more records
552
- sink(warning);
553
- assertEquals(mock.history().length, 3); // Not flushed yet
554
-
555
- // Wait for flush interval
556
- await delay(60);
557
- assertEquals(mock.history().length, 4); // Flushed due to interval
558
-
559
- // Dispose and check remaining records are flushed
560
- sink(error);
561
- sink(fatal);
562
- (sink as Sink & Disposable)[Symbol.dispose]();
563
- assertEquals(mock.history().length, 6); // All records flushed on dispose
564
- });
565
-
566
- test("getConsoleSink() with nonBlocking - no operations after dispose", () => {
567
- // @ts-ignore: consolemock is not typed
568
- const mock: ConsoleMock = makeConsoleMock();
569
- const sink = getConsoleSink({ console: mock, nonBlocking: true });
570
-
571
- // Dispose immediately
572
- (sink as Sink & Disposable)[Symbol.dispose]();
573
-
574
- // Try to add records after dispose
575
- sink(trace);
576
- sink(debug);
577
-
578
- // No records should be logged
579
- assertEquals(mock.history().length, 0);
580
- });
581
-
582
- test("getConsoleSink() with nonBlocking - error handling", async () => {
583
- const errorConsole = {
584
- ...console,
585
- debug: () => {
586
- throw new Error("Console error");
587
- },
588
- info: () => {
589
- throw new Error("Console error");
590
- },
591
- warn: () => {
592
- throw new Error("Console error");
593
- },
594
- error: () => {
595
- throw new Error("Console error");
596
- },
597
- };
598
-
599
- const sink = getConsoleSink({
600
- console: errorConsole,
601
- nonBlocking: true,
602
- });
603
-
604
- // Should not throw when adding records
605
- sink(trace);
606
- sink(info);
607
- sink(error);
608
-
609
- // Wait for flush - errors should be silently ignored
610
- await delay(150);
611
-
612
- // Dispose - should not throw
613
- (sink as Sink & Disposable)[Symbol.dispose]();
614
- });
615
-
616
- test("getConsoleSink() with nonBlocking - buffer overflow protection", async () => {
617
- // @ts-ignore: consolemock is not typed
618
- const mock: ConsoleMock = makeConsoleMock();
619
- const sink = getConsoleSink({
620
- console: mock,
621
- nonBlocking: {
622
- bufferSize: 5,
623
- flushInterval: 1000, // Long interval to prevent automatic flushing
624
- },
625
- });
626
-
627
- // Add more records than 2x buffer size (which should trigger overflow protection)
628
- for (let i = 0; i < 12; i++) {
629
- sink(trace);
630
- }
631
-
632
- // Should have dropped oldest records, keeping buffer size manageable
633
- // Wait a bit for any scheduled flushes
634
- await delay(10);
635
-
636
- // Force flush by disposing
637
- (sink as Sink & Disposable)[Symbol.dispose]();
638
-
639
- // Should have logged records, but not more than maxBufferSize (10)
640
- const historyLength = mock.history().length;
641
- assert(historyLength <= 10, `Expected <= 10 records, got ${historyLength}`);
642
- assert(historyLength > 0, "Expected some records to be logged");
643
- });
644
-
645
- test("getConsoleSink() with nonBlocking - high volume non-blocking behavior", async () => {
646
- // @ts-ignore: consolemock is not typed
647
- const mock: ConsoleMock = makeConsoleMock();
648
- const sink = getConsoleSink({
649
- console: mock,
650
- nonBlocking: {
651
- bufferSize: 3,
652
- flushInterval: 50,
653
- },
654
- });
655
-
656
- // Simulate rapid logging - this should not block
657
- const startTime = performance.now();
658
- for (let i = 0; i < 100; i++) {
659
- sink(trace);
660
- }
661
- const endTime = performance.now();
662
-
663
- // Adding logs should be very fast (non-blocking)
664
- const duration = endTime - startTime;
665
- assert(
666
- duration < 100,
667
- `Adding 100 logs took ${duration}ms, should be much faster`,
668
- );
669
-
670
- // Wait for flushes to complete
671
- await delay(200);
672
-
673
- // Should have logged some records
674
- assert(mock.history().length > 0, "Expected some records to be logged");
675
-
676
- (sink as Sink & Disposable)[Symbol.dispose]();
677
- });
678
-
679
260
  test("withBuffer() - buffer size limit", async () => {
680
261
  const buffer: LogRecord[] = [];
681
262
  const sink = withBuffer(buffer.push.bind(buffer), { bufferSize: 3 });
@@ -800,7 +381,7 @@ test("withBuffer() - disposes underlying AsyncDisposable sink", async () => {
800
381
 
801
382
  await bufferedSink[Symbol.asyncDispose]();
802
383
 
803
- assert(disposed); // Underlying sink should be disposed
384
+ assertEquals(disposed, true); // Underlying sink should be disposed
804
385
  });
805
386
 
806
387
  test("withBuffer() - disposes underlying Disposable sink", async () => {
@@ -818,7 +399,7 @@ test("withBuffer() - disposes underlying Disposable sink", async () => {
818
399
 
819
400
  await bufferedSink[Symbol.asyncDispose]();
820
401
 
821
- assert(disposed); // Underlying sink should be disposed
402
+ assertEquals(disposed, true); // Underlying sink should be disposed
822
403
  });
823
404
 
824
405
  test("withBuffer() - handles non-disposable sink gracefully", async () => {
@@ -833,7 +414,7 @@ test("withBuffer() - handles non-disposable sink gracefully", async () => {
833
414
  await bufferedSink[Symbol.asyncDispose]();
834
415
 
835
416
  // This test passes if no error is thrown
836
- assert(true);
417
+ assertEquals(true, true);
837
418
  });
838
419
 
839
420
  test("withBuffer() - edge case: bufferSize 1", async () => {
@@ -1016,7 +597,7 @@ test("withBuffer() - edge case: underlying AsyncDisposable throws error", async
1016
597
  } catch (error) {
1017
598
  assertInstanceOf(error, Error);
1018
599
  assertEquals(error.message, "Dispose error");
1019
- assert(disposed); // Should still be disposed
600
+ assertEquals(disposed, true); // Should still be disposed
1020
601
  assertEquals(buffer.length, 1); // Buffer should have been flushed before dispose error
1021
602
  }
1022
603
  });
@@ -1253,5 +834,5 @@ test("fromAsyncSink() - empty async sink", async () => {
1253
834
  await sink[Symbol.asyncDispose]();
1254
835
 
1255
836
  // Test passes if no errors thrown
1256
- assert(true);
837
+ assertEquals(true, true);
1257
838
  });