@logtape/file 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mod.d.cts +1 -1
- package/package.json +5 -2
- package/deno.json +0 -37
- package/dist/dist/filesink.base.d.cts +0 -74
- package/dist/dist/filesink.base.d.cts.map +0 -1
- package/dist/dist/filesink.node.d.cts +0 -45
- package/dist/dist/filesink.node.d.cts.map +0 -1
- package/src/filesink.base.ts +0 -815
- package/src/filesink.deno.ts +0 -122
- package/src/filesink.jsr.ts +0 -77
- package/src/filesink.node.ts +0 -118
- package/src/filesink.test.ts +0 -820
- package/src/mod.ts +0 -9
- package/src/streamfilesink.test.ts +0 -340
- package/src/streamfilesink.ts +0 -157
- package/tsdown.config.ts +0 -22
package/src/filesink.test.ts
DELETED
|
@@ -1,820 +0,0 @@
|
|
|
1
|
-
import { getFileSink, getRotatingFileSink } from "#filesink";
|
|
2
|
-
import { suite } from "@alinea/suite";
|
|
3
|
-
import { isDeno } from "@david/which-runtime";
|
|
4
|
-
import type { Sink } from "@logtape/logtape";
|
|
5
|
-
import { assert } from "@std/assert/assert";
|
|
6
|
-
import { assertEquals } from "@std/assert/equals";
|
|
7
|
-
import { assertThrows } from "@std/assert/throws";
|
|
8
|
-
import { delay } from "@std/async/delay";
|
|
9
|
-
import { join } from "@std/path/join";
|
|
10
|
-
import fs from "node:fs";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import {
|
|
13
|
-
debug,
|
|
14
|
-
error,
|
|
15
|
-
fatal,
|
|
16
|
-
info,
|
|
17
|
-
warning,
|
|
18
|
-
} from "../../logtape/src/fixtures.ts";
|
|
19
|
-
import { type FileSinkDriver, getBaseFileSink } from "./filesink.base.ts";
|
|
20
|
-
|
|
21
|
-
const test = suite(import.meta);
|
|
22
|
-
|
|
23
|
-
function makeTempFileSync(): string {
|
|
24
|
-
return join(fs.mkdtempSync(join(tmpdir(), "logtape-")), "logtape.txt");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
test("getBaseFileSink()", () => {
|
|
28
|
-
const path = makeTempFileSync();
|
|
29
|
-
let sink: Sink & Disposable;
|
|
30
|
-
if (isDeno) {
|
|
31
|
-
const driver: FileSinkDriver<Deno.FsFile> = {
|
|
32
|
-
openSync(path: string) {
|
|
33
|
-
return Deno.openSync(path, { create: true, append: true });
|
|
34
|
-
},
|
|
35
|
-
writeSync(fd, chunk) {
|
|
36
|
-
fd.writeSync(chunk);
|
|
37
|
-
},
|
|
38
|
-
flushSync(fd) {
|
|
39
|
-
fd.syncSync();
|
|
40
|
-
},
|
|
41
|
-
closeSync(fd) {
|
|
42
|
-
fd.close();
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
sink = getBaseFileSink(path, driver);
|
|
46
|
-
} else {
|
|
47
|
-
const driver: FileSinkDriver<number> = {
|
|
48
|
-
openSync(path: string) {
|
|
49
|
-
return fs.openSync(path, "a");
|
|
50
|
-
},
|
|
51
|
-
writeSync: fs.writeSync,
|
|
52
|
-
flushSync: fs.fsyncSync,
|
|
53
|
-
closeSync: fs.closeSync,
|
|
54
|
-
};
|
|
55
|
-
sink = getBaseFileSink(path, driver);
|
|
56
|
-
}
|
|
57
|
-
sink(debug);
|
|
58
|
-
sink(info);
|
|
59
|
-
sink(warning);
|
|
60
|
-
sink(error);
|
|
61
|
-
sink(fatal);
|
|
62
|
-
sink[Symbol.dispose]();
|
|
63
|
-
assertEquals(
|
|
64
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
65
|
-
`\
|
|
66
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
67
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
68
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
69
|
-
2023-11-14 22:13:20.000 +00:00 [ERR] my-app·junk: Hello, 123 & 456!
|
|
70
|
-
2023-11-14 22:13:20.000 +00:00 [FTL] my-app·junk: Hello, 123 & 456!
|
|
71
|
-
`,
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("getBaseFileSink() with lazy option", () => {
|
|
76
|
-
const pathDir = fs.mkdtempSync(join(tmpdir(), "logtape-"));
|
|
77
|
-
const path = join(pathDir, "test.log");
|
|
78
|
-
let sink: Sink & Disposable;
|
|
79
|
-
if (isDeno) {
|
|
80
|
-
const driver: FileSinkDriver<Deno.FsFile> = {
|
|
81
|
-
openSync(path: string) {
|
|
82
|
-
return Deno.openSync(path, { create: true, append: true });
|
|
83
|
-
},
|
|
84
|
-
writeSync(fd, chunk) {
|
|
85
|
-
fd.writeSync(chunk);
|
|
86
|
-
},
|
|
87
|
-
flushSync(fd) {
|
|
88
|
-
fd.syncSync();
|
|
89
|
-
},
|
|
90
|
-
closeSync(fd) {
|
|
91
|
-
fd.close();
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
sink = getBaseFileSink(path, { ...driver, lazy: true });
|
|
95
|
-
} else {
|
|
96
|
-
const driver: FileSinkDriver<number> = {
|
|
97
|
-
openSync(path: string) {
|
|
98
|
-
return fs.openSync(path, "a");
|
|
99
|
-
},
|
|
100
|
-
writeSync: fs.writeSync,
|
|
101
|
-
flushSync: fs.fsyncSync,
|
|
102
|
-
closeSync: fs.closeSync,
|
|
103
|
-
};
|
|
104
|
-
sink = getBaseFileSink(path, { ...driver, lazy: true });
|
|
105
|
-
}
|
|
106
|
-
if (isDeno) {
|
|
107
|
-
assertThrows(
|
|
108
|
-
() => Deno.lstatSync(path),
|
|
109
|
-
Deno.errors.NotFound,
|
|
110
|
-
);
|
|
111
|
-
} else {
|
|
112
|
-
assertEquals(fs.existsSync(path), false);
|
|
113
|
-
}
|
|
114
|
-
sink(debug);
|
|
115
|
-
sink(info);
|
|
116
|
-
sink(warning);
|
|
117
|
-
sink(error);
|
|
118
|
-
sink(fatal);
|
|
119
|
-
sink[Symbol.dispose]();
|
|
120
|
-
assertEquals(
|
|
121
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
122
|
-
`\
|
|
123
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
124
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
125
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
126
|
-
2023-11-14 22:13:20.000 +00:00 [ERR] my-app·junk: Hello, 123 & 456!
|
|
127
|
-
2023-11-14 22:13:20.000 +00:00 [FTL] my-app·junk: Hello, 123 & 456!
|
|
128
|
-
`,
|
|
129
|
-
);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("getFileSink()", () => {
|
|
133
|
-
const path = makeTempFileSync();
|
|
134
|
-
const sink: Sink & Disposable = getFileSink(path);
|
|
135
|
-
sink(debug);
|
|
136
|
-
sink(info);
|
|
137
|
-
sink(warning);
|
|
138
|
-
sink(error);
|
|
139
|
-
sink(fatal);
|
|
140
|
-
sink[Symbol.dispose]();
|
|
141
|
-
assertEquals(
|
|
142
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
143
|
-
`\
|
|
144
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
145
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
146
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
147
|
-
2023-11-14 22:13:20.000 +00:00 [ERR] my-app·junk: Hello, 123 & 456!
|
|
148
|
-
2023-11-14 22:13:20.000 +00:00 [FTL] my-app·junk: Hello, 123 & 456!
|
|
149
|
-
`,
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("getFileSink() with bufferSize: 0 (no buffering)", () => {
|
|
154
|
-
const path = makeTempFileSync();
|
|
155
|
-
const sink: Sink & Disposable = getFileSink(path, { bufferSize: 0 });
|
|
156
|
-
|
|
157
|
-
// Write first log entry
|
|
158
|
-
sink(debug);
|
|
159
|
-
// With no buffering, content should be immediately written to file
|
|
160
|
-
assertEquals(
|
|
161
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
162
|
-
"2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!\n",
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
// Write second log entry
|
|
166
|
-
sink(info);
|
|
167
|
-
assertEquals(
|
|
168
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
169
|
-
`\
|
|
170
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
171
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
172
|
-
`,
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// Write remaining entries
|
|
176
|
-
sink(warning);
|
|
177
|
-
sink(error);
|
|
178
|
-
sink(fatal);
|
|
179
|
-
sink[Symbol.dispose]();
|
|
180
|
-
|
|
181
|
-
// Final verification
|
|
182
|
-
assertEquals(
|
|
183
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
184
|
-
`\
|
|
185
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
186
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
187
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
188
|
-
2023-11-14 22:13:20.000 +00:00 [ERR] my-app·junk: Hello, 123 & 456!
|
|
189
|
-
2023-11-14 22:13:20.000 +00:00 [FTL] my-app·junk: Hello, 123 & 456!
|
|
190
|
-
`,
|
|
191
|
-
);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("getFileSink() with small buffer size", () => {
|
|
195
|
-
const path = makeTempFileSync();
|
|
196
|
-
// Use a small buffer size (100 characters) to test buffering behavior
|
|
197
|
-
const sink: Sink & Disposable = getFileSink(path, { bufferSize: 100 });
|
|
198
|
-
|
|
199
|
-
// Write first log entry (about 65 characters)
|
|
200
|
-
sink(debug);
|
|
201
|
-
// Should be buffered, not yet written to file
|
|
202
|
-
assertEquals(fs.readFileSync(path, { encoding: "utf-8" }), "");
|
|
203
|
-
|
|
204
|
-
// Write second log entry - this should exceed buffer size and trigger flush
|
|
205
|
-
sink(info);
|
|
206
|
-
// Both entries should now be written to file
|
|
207
|
-
assertEquals(
|
|
208
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
209
|
-
`\
|
|
210
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
211
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
212
|
-
`,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
// Write third log entry - should be buffered again
|
|
216
|
-
sink(warning);
|
|
217
|
-
// Should still only have the first two entries
|
|
218
|
-
assertEquals(
|
|
219
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
220
|
-
`\
|
|
221
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
222
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
223
|
-
`,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
// Dispose should flush remaining buffer content
|
|
227
|
-
sink[Symbol.dispose]();
|
|
228
|
-
assertEquals(
|
|
229
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
230
|
-
`\
|
|
231
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
232
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
233
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
234
|
-
`,
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("getRotatingFileSink() with bufferSize: 0 (no buffering)", () => {
|
|
239
|
-
const path = makeTempFileSync();
|
|
240
|
-
const sink: Sink & Disposable = getRotatingFileSink(path, {
|
|
241
|
-
maxSize: 150,
|
|
242
|
-
bufferSize: 0, // No buffering - immediate writes
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// Write first log entry - should be immediately written
|
|
246
|
-
sink(debug);
|
|
247
|
-
assertEquals(
|
|
248
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
249
|
-
"2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!\n",
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
// Write second log entry - should be immediately written
|
|
253
|
-
sink(info);
|
|
254
|
-
assertEquals(
|
|
255
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
256
|
-
`\
|
|
257
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
258
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
259
|
-
`,
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
// Write third log entry - should trigger rotation
|
|
263
|
-
sink(warning);
|
|
264
|
-
assertEquals(
|
|
265
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
266
|
-
"2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!\n",
|
|
267
|
-
);
|
|
268
|
-
// Check that rotation occurred
|
|
269
|
-
assertEquals(
|
|
270
|
-
fs.readFileSync(`${path}.1`, { encoding: "utf-8" }),
|
|
271
|
-
`\
|
|
272
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
273
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
274
|
-
`,
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
sink[Symbol.dispose]();
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test("getRotatingFileSink() with small buffer size", () => {
|
|
281
|
-
const path = makeTempFileSync();
|
|
282
|
-
const sink: Sink & Disposable = getRotatingFileSink(path, {
|
|
283
|
-
maxSize: 200, // Larger maxSize to allow for buffering tests
|
|
284
|
-
bufferSize: 100, // Small buffer to test interaction with rotation
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// Write first log entry - should be buffered
|
|
288
|
-
sink(debug);
|
|
289
|
-
assertEquals(fs.readFileSync(path, { encoding: "utf-8" }), "");
|
|
290
|
-
|
|
291
|
-
// Write second log entry - should trigger buffer flush
|
|
292
|
-
sink(info);
|
|
293
|
-
assertEquals(
|
|
294
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
295
|
-
`\
|
|
296
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
297
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
298
|
-
`,
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
// Write third log entry - should be buffered again
|
|
302
|
-
sink(warning);
|
|
303
|
-
assertEquals(
|
|
304
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
305
|
-
`\
|
|
306
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
307
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
308
|
-
`,
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
// Write fourth log entry - should flush buffer and likely trigger rotation
|
|
312
|
-
sink(error);
|
|
313
|
-
|
|
314
|
-
// Dispose should flush any remaining buffer content
|
|
315
|
-
sink[Symbol.dispose]();
|
|
316
|
-
|
|
317
|
-
// Verify final state - all entries should be written somewhere
|
|
318
|
-
const mainContent = fs.readFileSync(path, { encoding: "utf-8" });
|
|
319
|
-
let rotatedContent = "";
|
|
320
|
-
try {
|
|
321
|
-
rotatedContent = fs.readFileSync(`${path}.1`, { encoding: "utf-8" });
|
|
322
|
-
} catch {
|
|
323
|
-
// No rotation occurred
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const allContent = mainContent + rotatedContent;
|
|
327
|
-
// All four log entries should be present exactly once in either main or rotated file
|
|
328
|
-
const expectedEntries = [
|
|
329
|
-
"2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!\n",
|
|
330
|
-
"2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!\n",
|
|
331
|
-
"2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!\n",
|
|
332
|
-
"2023-11-14 22:13:20.000 +00:00 [ERR] my-app·junk: Hello, 123 & 456!\n",
|
|
333
|
-
];
|
|
334
|
-
|
|
335
|
-
for (const entry of expectedEntries) {
|
|
336
|
-
assertEquals(
|
|
337
|
-
allContent.includes(entry),
|
|
338
|
-
true,
|
|
339
|
-
`Missing log entry: ${entry.trim()}`,
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Verify each entry appears exactly once
|
|
344
|
-
for (const entry of expectedEntries) {
|
|
345
|
-
const firstIndex = allContent.indexOf(entry);
|
|
346
|
-
const lastIndex = allContent.lastIndexOf(entry);
|
|
347
|
-
assertEquals(firstIndex, lastIndex, `Duplicate log entry: ${entry.trim()}`);
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
test("getRotatingFileSink()", () => {
|
|
352
|
-
const path = makeTempFileSync();
|
|
353
|
-
const sink: Sink & Disposable = getRotatingFileSink(path, {
|
|
354
|
-
maxSize: 150,
|
|
355
|
-
bufferSize: 0, // Disable buffering for this test to maintain existing behavior
|
|
356
|
-
});
|
|
357
|
-
sink(debug);
|
|
358
|
-
assertEquals(
|
|
359
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
360
|
-
"2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!\n",
|
|
361
|
-
);
|
|
362
|
-
sink(info);
|
|
363
|
-
assertEquals(
|
|
364
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
365
|
-
`\
|
|
366
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
367
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
368
|
-
`,
|
|
369
|
-
);
|
|
370
|
-
sink(warning);
|
|
371
|
-
assertEquals(
|
|
372
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
373
|
-
"2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!\n",
|
|
374
|
-
);
|
|
375
|
-
assertEquals(
|
|
376
|
-
fs.readFileSync(`${path}.1`, { encoding: "utf-8" }),
|
|
377
|
-
`\
|
|
378
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
379
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
380
|
-
`,
|
|
381
|
-
);
|
|
382
|
-
sink(error);
|
|
383
|
-
assertEquals(
|
|
384
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
385
|
-
`\
|
|
386
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
387
|
-
2023-11-14 22:13:20.000 +00:00 [ERR] my-app·junk: Hello, 123 & 456!
|
|
388
|
-
`,
|
|
389
|
-
);
|
|
390
|
-
assertEquals(
|
|
391
|
-
fs.readFileSync(`${path}.1`, { encoding: "utf-8" }),
|
|
392
|
-
`\
|
|
393
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
394
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
395
|
-
`,
|
|
396
|
-
);
|
|
397
|
-
sink(fatal);
|
|
398
|
-
sink[Symbol.dispose]();
|
|
399
|
-
assertEquals(
|
|
400
|
-
fs.readFileSync(path, { encoding: "utf-8" }),
|
|
401
|
-
"2023-11-14 22:13:20.000 +00:00 [FTL] my-app·junk: Hello, 123 & 456!\n",
|
|
402
|
-
);
|
|
403
|
-
assertEquals(
|
|
404
|
-
fs.readFileSync(`${path}.1`, { encoding: "utf-8" }),
|
|
405
|
-
`\
|
|
406
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
407
|
-
2023-11-14 22:13:20.000 +00:00 [ERR] my-app·junk: Hello, 123 & 456!
|
|
408
|
-
`,
|
|
409
|
-
);
|
|
410
|
-
assertEquals(
|
|
411
|
-
fs.readFileSync(`${path}.2`, { encoding: "utf-8" }),
|
|
412
|
-
`\
|
|
413
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
414
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
415
|
-
`,
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
const dirPath = fs.mkdtempSync(join(tmpdir(), "logtape-"));
|
|
419
|
-
const path2 = join(dirPath, "log");
|
|
420
|
-
const sink2: Sink & Disposable = getRotatingFileSink(path2, {
|
|
421
|
-
maxSize: 150,
|
|
422
|
-
bufferSize: 0, // Disable buffering for this test to maintain existing behavior
|
|
423
|
-
});
|
|
424
|
-
sink2(debug);
|
|
425
|
-
assertEquals(
|
|
426
|
-
fs.readFileSync(path2, { encoding: "utf-8" }),
|
|
427
|
-
"2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!\n",
|
|
428
|
-
);
|
|
429
|
-
sink2[Symbol.dispose]();
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
test("getBaseFileSink() with buffer edge cases", () => {
|
|
433
|
-
// Test negative bufferSize (should behave like bufferSize: 0)
|
|
434
|
-
const path1 = makeTempFileSync();
|
|
435
|
-
let sink1: Sink & Disposable;
|
|
436
|
-
if (isDeno) {
|
|
437
|
-
const driver: FileSinkDriver<Deno.FsFile> = {
|
|
438
|
-
openSync(path: string) {
|
|
439
|
-
return Deno.openSync(path, { create: true, append: true });
|
|
440
|
-
},
|
|
441
|
-
writeSync(fd, chunk) {
|
|
442
|
-
fd.writeSync(chunk);
|
|
443
|
-
},
|
|
444
|
-
flushSync(fd) {
|
|
445
|
-
fd.syncSync();
|
|
446
|
-
},
|
|
447
|
-
closeSync(fd) {
|
|
448
|
-
fd.close();
|
|
449
|
-
},
|
|
450
|
-
};
|
|
451
|
-
sink1 = getBaseFileSink(path1, { ...driver, bufferSize: -10 });
|
|
452
|
-
} else {
|
|
453
|
-
const driver: FileSinkDriver<number> = {
|
|
454
|
-
openSync(path: string) {
|
|
455
|
-
return fs.openSync(path, "a");
|
|
456
|
-
},
|
|
457
|
-
writeSync: fs.writeSync,
|
|
458
|
-
flushSync: fs.fsyncSync,
|
|
459
|
-
closeSync: fs.closeSync,
|
|
460
|
-
};
|
|
461
|
-
sink1 = getBaseFileSink(path1, { ...driver, bufferSize: -10 });
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
sink1(debug);
|
|
465
|
-
// With negative bufferSize, should write immediately
|
|
466
|
-
assertEquals(
|
|
467
|
-
fs.readFileSync(path1, { encoding: "utf-8" }),
|
|
468
|
-
"2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!\n",
|
|
469
|
-
);
|
|
470
|
-
sink1[Symbol.dispose]();
|
|
471
|
-
|
|
472
|
-
// Test bufferSize of 1 (very small)
|
|
473
|
-
const path2 = makeTempFileSync();
|
|
474
|
-
let sink2: Sink & Disposable;
|
|
475
|
-
if (isDeno) {
|
|
476
|
-
const driver: FileSinkDriver<Deno.FsFile> = {
|
|
477
|
-
openSync(path: string) {
|
|
478
|
-
return Deno.openSync(path, { create: true, append: true });
|
|
479
|
-
},
|
|
480
|
-
writeSync(fd, chunk) {
|
|
481
|
-
fd.writeSync(chunk);
|
|
482
|
-
},
|
|
483
|
-
flushSync(fd) {
|
|
484
|
-
fd.syncSync();
|
|
485
|
-
},
|
|
486
|
-
closeSync(fd) {
|
|
487
|
-
fd.close();
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
sink2 = getBaseFileSink(path2, { ...driver, bufferSize: 1 });
|
|
491
|
-
} else {
|
|
492
|
-
const driver: FileSinkDriver<number> = {
|
|
493
|
-
openSync(path: string) {
|
|
494
|
-
return fs.openSync(path, "a");
|
|
495
|
-
},
|
|
496
|
-
writeSync: fs.writeSync,
|
|
497
|
-
flushSync: fs.fsyncSync,
|
|
498
|
-
closeSync: fs.closeSync,
|
|
499
|
-
};
|
|
500
|
-
sink2 = getBaseFileSink(path2, { ...driver, bufferSize: 1 });
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
sink2(debug);
|
|
504
|
-
// With bufferSize of 1, should write immediately since log entry > 1 char
|
|
505
|
-
assertEquals(
|
|
506
|
-
fs.readFileSync(path2, { encoding: "utf-8" }),
|
|
507
|
-
"2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!\n",
|
|
508
|
-
);
|
|
509
|
-
sink2[Symbol.dispose]();
|
|
510
|
-
|
|
511
|
-
// Test very large bufferSize
|
|
512
|
-
const path3 = makeTempFileSync();
|
|
513
|
-
let sink3: Sink & Disposable;
|
|
514
|
-
if (isDeno) {
|
|
515
|
-
const driver: FileSinkDriver<Deno.FsFile> = {
|
|
516
|
-
openSync(path: string) {
|
|
517
|
-
return Deno.openSync(path, { create: true, append: true });
|
|
518
|
-
},
|
|
519
|
-
writeSync(fd, chunk) {
|
|
520
|
-
fd.writeSync(chunk);
|
|
521
|
-
},
|
|
522
|
-
flushSync(fd) {
|
|
523
|
-
fd.syncSync();
|
|
524
|
-
},
|
|
525
|
-
closeSync(fd) {
|
|
526
|
-
fd.close();
|
|
527
|
-
},
|
|
528
|
-
};
|
|
529
|
-
sink3 = getBaseFileSink(path3, { ...driver, bufferSize: 10000 });
|
|
530
|
-
} else {
|
|
531
|
-
const driver: FileSinkDriver<number> = {
|
|
532
|
-
openSync(path: string) {
|
|
533
|
-
return fs.openSync(path, "a");
|
|
534
|
-
},
|
|
535
|
-
writeSync: fs.writeSync,
|
|
536
|
-
flushSync: fs.fsyncSync,
|
|
537
|
-
closeSync: fs.closeSync,
|
|
538
|
-
};
|
|
539
|
-
sink3 = getBaseFileSink(path3, { ...driver, bufferSize: 10000 });
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Write multiple entries that shouldn't exceed the large buffer
|
|
543
|
-
sink3(debug);
|
|
544
|
-
sink3(info);
|
|
545
|
-
sink3(warning);
|
|
546
|
-
// Should still be buffered (file empty)
|
|
547
|
-
assertEquals(fs.readFileSync(path3, { encoding: "utf-8" }), "");
|
|
548
|
-
|
|
549
|
-
// Dispose should flush all buffered content
|
|
550
|
-
sink3[Symbol.dispose]();
|
|
551
|
-
assertEquals(
|
|
552
|
-
fs.readFileSync(path3, { encoding: "utf-8" }),
|
|
553
|
-
`\
|
|
554
|
-
2023-11-14 22:13:20.000 +00:00 [DBG] my-app·junk: Hello, 123 & 456!
|
|
555
|
-
2023-11-14 22:13:20.000 +00:00 [INF] my-app·junk: Hello, 123 & 456!
|
|
556
|
-
2023-11-14 22:13:20.000 +00:00 [WRN] my-app·junk: Hello, 123 & 456!
|
|
557
|
-
`,
|
|
558
|
-
);
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
test("getBaseFileSink() with time-based flushing", async () => {
|
|
562
|
-
const path = makeTempFileSync();
|
|
563
|
-
let sink: Sink & Disposable;
|
|
564
|
-
if (isDeno) {
|
|
565
|
-
const driver: FileSinkDriver<Deno.FsFile> = {
|
|
566
|
-
openSync(path: string) {
|
|
567
|
-
return Deno.openSync(path, { create: true, append: true });
|
|
568
|
-
},
|
|
569
|
-
writeSync(fd, chunk) {
|
|
570
|
-
fd.writeSync(chunk);
|
|
571
|
-
},
|
|
572
|
-
flushSync(fd) {
|
|
573
|
-
fd.syncSync();
|
|
574
|
-
},
|
|
575
|
-
closeSync(fd) {
|
|
576
|
-
fd.close();
|
|
577
|
-
},
|
|
578
|
-
};
|
|
579
|
-
sink = getBaseFileSink(path, {
|
|
580
|
-
...driver,
|
|
581
|
-
bufferSize: 1000, // Large buffer to prevent size-based flushing
|
|
582
|
-
flushInterval: 100, // 100ms flush interval for testing
|
|
583
|
-
});
|
|
584
|
-
} else {
|
|
585
|
-
const driver: FileSinkDriver<number> = {
|
|
586
|
-
openSync(path: string) {
|
|
587
|
-
return fs.openSync(path, "a");
|
|
588
|
-
},
|
|
589
|
-
writeSync: fs.writeSync,
|
|
590
|
-
flushSync: fs.fsyncSync,
|
|
591
|
-
closeSync: fs.closeSync,
|
|
592
|
-
};
|
|
593
|
-
sink = getBaseFileSink(path, {
|
|
594
|
-
...driver,
|
|
595
|
-
bufferSize: 1000, // Large buffer to prevent size-based flushing
|
|
596
|
-
flushInterval: 100, // 100ms flush interval for testing
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Create a log record with current timestamp
|
|
601
|
-
const record1 = { ...debug, timestamp: Date.now() };
|
|
602
|
-
sink(record1);
|
|
603
|
-
|
|
604
|
-
// Should be buffered (file empty initially)
|
|
605
|
-
assertEquals(fs.readFileSync(path, { encoding: "utf-8" }), "");
|
|
606
|
-
|
|
607
|
-
// Wait for flush interval to pass and write another record
|
|
608
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
609
|
-
const record2 = { ...info, timestamp: Date.now() };
|
|
610
|
-
sink(record2);
|
|
611
|
-
|
|
612
|
-
// First record should now be flushed due to time interval
|
|
613
|
-
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
614
|
-
assertEquals(content.includes("Hello, 123 & 456!"), true);
|
|
615
|
-
|
|
616
|
-
sink[Symbol.dispose]();
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
test("getRotatingFileSink() with time-based flushing", async () => {
|
|
620
|
-
const path = makeTempFileSync();
|
|
621
|
-
const sink: Sink & Disposable = getRotatingFileSink(path, {
|
|
622
|
-
maxSize: 1024 * 1024, // Large maxSize to prevent rotation
|
|
623
|
-
bufferSize: 1000, // Large buffer to prevent size-based flushing
|
|
624
|
-
flushInterval: 100, // 100ms flush interval for testing
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
// Create a log record with current timestamp
|
|
628
|
-
const record1 = { ...debug, timestamp: Date.now() };
|
|
629
|
-
sink(record1);
|
|
630
|
-
|
|
631
|
-
// Should be buffered (file empty initially)
|
|
632
|
-
assertEquals(fs.readFileSync(path, { encoding: "utf-8" }), "");
|
|
633
|
-
|
|
634
|
-
// Wait for flush interval to pass and write another record
|
|
635
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
636
|
-
const record2 = { ...info, timestamp: Date.now() };
|
|
637
|
-
sink(record2);
|
|
638
|
-
|
|
639
|
-
// First record should now be flushed due to time interval
|
|
640
|
-
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
641
|
-
assertEquals(content.includes("Hello, 123 & 456!"), true);
|
|
642
|
-
|
|
643
|
-
sink[Symbol.dispose]();
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
test("getBaseFileSink() with flushInterval disabled", () => {
|
|
647
|
-
const path = makeTempFileSync();
|
|
648
|
-
let sink: Sink & Disposable;
|
|
649
|
-
if (isDeno) {
|
|
650
|
-
const driver: FileSinkDriver<Deno.FsFile> = {
|
|
651
|
-
openSync(path: string) {
|
|
652
|
-
return Deno.openSync(path, { create: true, append: true });
|
|
653
|
-
},
|
|
654
|
-
writeSync(fd, chunk) {
|
|
655
|
-
fd.writeSync(chunk);
|
|
656
|
-
},
|
|
657
|
-
flushSync(fd) {
|
|
658
|
-
fd.syncSync();
|
|
659
|
-
},
|
|
660
|
-
closeSync(fd) {
|
|
661
|
-
fd.close();
|
|
662
|
-
},
|
|
663
|
-
};
|
|
664
|
-
sink = getBaseFileSink(path, {
|
|
665
|
-
...driver,
|
|
666
|
-
bufferSize: 1000, // Large buffer to prevent size-based flushing
|
|
667
|
-
flushInterval: 0, // Disable time-based flushing
|
|
668
|
-
});
|
|
669
|
-
} else {
|
|
670
|
-
const driver: FileSinkDriver<number> = {
|
|
671
|
-
openSync(path: string) {
|
|
672
|
-
return fs.openSync(path, "a");
|
|
673
|
-
},
|
|
674
|
-
writeSync: fs.writeSync,
|
|
675
|
-
flushSync: fs.fsyncSync,
|
|
676
|
-
closeSync: fs.closeSync,
|
|
677
|
-
};
|
|
678
|
-
sink = getBaseFileSink(path, {
|
|
679
|
-
...driver,
|
|
680
|
-
bufferSize: 1000, // Large buffer to prevent size-based flushing
|
|
681
|
-
flushInterval: 0, // Disable time-based flushing
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// Create log records with simulated time gap
|
|
686
|
-
const now = Date.now();
|
|
687
|
-
const record1 = { ...debug, timestamp: now };
|
|
688
|
-
const record2 = { ...info, timestamp: now + 10000 }; // 10 seconds later
|
|
689
|
-
|
|
690
|
-
sink(record1);
|
|
691
|
-
sink(record2);
|
|
692
|
-
|
|
693
|
-
// Should still be buffered since time-based flushing is disabled
|
|
694
|
-
assertEquals(fs.readFileSync(path, { encoding: "utf-8" }), "");
|
|
695
|
-
|
|
696
|
-
// Only disposal should flush
|
|
697
|
-
sink[Symbol.dispose]();
|
|
698
|
-
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
699
|
-
assertEquals(content.includes("Hello, 123 & 456!"), true);
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
test("getFileSink() with nonBlocking mode", async () => {
|
|
703
|
-
const path = makeTempFileSync();
|
|
704
|
-
const sink = getFileSink(path, {
|
|
705
|
-
nonBlocking: true,
|
|
706
|
-
bufferSize: 50, // Small buffer to trigger flush by size
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
// Check that it returns AsyncDisposable
|
|
710
|
-
assert(typeof sink === "function");
|
|
711
|
-
assert(Symbol.asyncDispose in sink);
|
|
712
|
-
|
|
713
|
-
// Add enough records to trigger buffer flush
|
|
714
|
-
sink(debug);
|
|
715
|
-
sink(info);
|
|
716
|
-
|
|
717
|
-
// Wait for async flush to complete
|
|
718
|
-
await delay(50);
|
|
719
|
-
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
720
|
-
assert(content.includes("Hello, 123 & 456!"));
|
|
721
|
-
|
|
722
|
-
await (sink as Sink & AsyncDisposable)[Symbol.asyncDispose]();
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
test("getRotatingFileSink() with nonBlocking mode", async () => {
|
|
726
|
-
const path = makeTempFileSync();
|
|
727
|
-
const sink = getRotatingFileSink(path, {
|
|
728
|
-
maxSize: 200,
|
|
729
|
-
nonBlocking: true,
|
|
730
|
-
bufferSize: 1000, // Large buffer to prevent immediate flush
|
|
731
|
-
flushInterval: 50, // Short interval for testing
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
// Check that it returns AsyncDisposable
|
|
735
|
-
assert(typeof sink === "function");
|
|
736
|
-
assert(Symbol.asyncDispose in sink);
|
|
737
|
-
|
|
738
|
-
// Add records with current timestamp
|
|
739
|
-
const record1 = { ...debug, timestamp: Date.now() };
|
|
740
|
-
const record2 = { ...info, timestamp: Date.now() };
|
|
741
|
-
sink(record1);
|
|
742
|
-
sink(record2);
|
|
743
|
-
assertEquals(fs.readFileSync(path, { encoding: "utf-8" }), ""); // Not written yet
|
|
744
|
-
|
|
745
|
-
// Wait for flush interval to pass
|
|
746
|
-
await delay(100);
|
|
747
|
-
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
748
|
-
assert(content.includes("Hello, 123 & 456!"));
|
|
749
|
-
|
|
750
|
-
await (sink as Sink & AsyncDisposable)[Symbol.asyncDispose]();
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
test("getFileSink() with nonBlocking high-volume logging", async () => {
|
|
754
|
-
const path = makeTempFileSync();
|
|
755
|
-
const sink = getFileSink(path, {
|
|
756
|
-
nonBlocking: true,
|
|
757
|
-
bufferSize: 50, // Small buffer to trigger flush
|
|
758
|
-
flushInterval: 0, // Disable time-based flushing for this test
|
|
759
|
-
}) as unknown as Sink & AsyncDisposable;
|
|
760
|
-
|
|
761
|
-
// Add enough records to trigger buffer flush (50 chars per record roughly)
|
|
762
|
-
let totalChars = 0;
|
|
763
|
-
let recordCount = 0;
|
|
764
|
-
while (totalChars < 100) { // Exceed buffer size
|
|
765
|
-
sink(debug);
|
|
766
|
-
totalChars += 67; // Approximate length of each debug record
|
|
767
|
-
recordCount++;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// Wait for async flush to complete
|
|
771
|
-
await delay(50);
|
|
772
|
-
const content = fs.readFileSync(path, { encoding: "utf-8" });
|
|
773
|
-
|
|
774
|
-
// Should have some records written by now
|
|
775
|
-
const writtenCount = (content.match(/Hello, 123 & 456!/g) || []).length;
|
|
776
|
-
assert(
|
|
777
|
-
writtenCount > 0,
|
|
778
|
-
`Expected some records to be written, but got ${writtenCount}`,
|
|
779
|
-
);
|
|
780
|
-
|
|
781
|
-
await sink[Symbol.asyncDispose]();
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
test("getRotatingFileSink() with nonBlocking rotation", async () => {
|
|
785
|
-
const path = makeTempFileSync();
|
|
786
|
-
const sink = getRotatingFileSink(path, {
|
|
787
|
-
maxSize: 150, // Small size to trigger rotation
|
|
788
|
-
nonBlocking: true,
|
|
789
|
-
bufferSize: 100,
|
|
790
|
-
flushInterval: 10,
|
|
791
|
-
}) as unknown as Sink & AsyncDisposable;
|
|
792
|
-
|
|
793
|
-
// Add enough records to trigger rotation
|
|
794
|
-
sink(debug);
|
|
795
|
-
sink(info);
|
|
796
|
-
sink(warning);
|
|
797
|
-
sink(error);
|
|
798
|
-
|
|
799
|
-
// Wait for all flushes and rotation to complete
|
|
800
|
-
await delay(200);
|
|
801
|
-
|
|
802
|
-
// Check that rotation occurred
|
|
803
|
-
const mainContent = fs.readFileSync(path, { encoding: "utf-8" });
|
|
804
|
-
let rotatedContent = "";
|
|
805
|
-
try {
|
|
806
|
-
rotatedContent = fs.readFileSync(`${path}.1`, { encoding: "utf-8" });
|
|
807
|
-
} catch {
|
|
808
|
-
// No rotation occurred
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const allContent = mainContent + rotatedContent;
|
|
812
|
-
|
|
813
|
-
// Should have all 4 records somewhere
|
|
814
|
-
const recordCount = (allContent.match(/Hello, 123 & 456!/g) || []).length;
|
|
815
|
-
assertEquals(recordCount, 4);
|
|
816
|
-
|
|
817
|
-
await sink[Symbol.asyncDispose]();
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
// cSpell: ignore filesink
|