@logtape/fastify 1.3.0-dev.1
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/LICENSE +20 -0
- package/deno.json +34 -0
- package/dist/_virtual/rolldown_runtime.cjs +30 -0
- package/dist/mod.cjs +123 -0
- package/dist/mod.d.cts +115 -0
- package/dist/mod.d.cts.map +1 -0
- package/dist/mod.d.ts +115 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +123 -0
- package/dist/mod.js.map +1 -0
- package/package.json +66 -0
- package/src/mod.test.ts +779 -0
- package/src/mod.ts +252 -0
- package/tsdown.config.ts +11 -0
package/src/mod.test.ts
ADDED
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import { suite } from "@alinea/suite";
|
|
2
|
+
import { assertEquals } from "@std/assert/equals";
|
|
3
|
+
import { configure, type LogRecord, reset } from "@logtape/logtape";
|
|
4
|
+
import { getLogTapeFastifyLogger } from "./mod.ts";
|
|
5
|
+
|
|
6
|
+
const test = suite(import.meta);
|
|
7
|
+
|
|
8
|
+
// Test fixture: Collect log records, filtering out internal LogTape meta logs
|
|
9
|
+
function createTestSink(): {
|
|
10
|
+
sink: (record: LogRecord) => void;
|
|
11
|
+
logs: LogRecord[];
|
|
12
|
+
} {
|
|
13
|
+
const logs: LogRecord[] = [];
|
|
14
|
+
return {
|
|
15
|
+
// Filter out logtape meta logs automatically
|
|
16
|
+
sink: (record: LogRecord) => {
|
|
17
|
+
if (record.category[0] !== "logtape") {
|
|
18
|
+
logs.push(record);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
logs,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Setup helper
|
|
26
|
+
async function setupLogtape(): Promise<{
|
|
27
|
+
logs: LogRecord[];
|
|
28
|
+
cleanup: () => Promise<void>;
|
|
29
|
+
}> {
|
|
30
|
+
const { sink, logs } = createTestSink();
|
|
31
|
+
await configure({
|
|
32
|
+
sinks: { test: sink },
|
|
33
|
+
loggers: [{ category: [], sinks: ["test"] }],
|
|
34
|
+
});
|
|
35
|
+
return { logs, cleanup: () => reset() };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================
|
|
39
|
+
// Basic Logger Creation Tests
|
|
40
|
+
// ============================================
|
|
41
|
+
|
|
42
|
+
test("getLogTapeFastifyLogger(): creates a Pino-like logger", async () => {
|
|
43
|
+
const { cleanup } = await setupLogtape();
|
|
44
|
+
try {
|
|
45
|
+
const logger = getLogTapeFastifyLogger();
|
|
46
|
+
|
|
47
|
+
assertEquals(typeof logger.info, "function");
|
|
48
|
+
assertEquals(typeof logger.error, "function");
|
|
49
|
+
assertEquals(typeof logger.debug, "function");
|
|
50
|
+
assertEquals(typeof logger.warn, "function");
|
|
51
|
+
assertEquals(typeof logger.trace, "function");
|
|
52
|
+
assertEquals(typeof logger.fatal, "function");
|
|
53
|
+
assertEquals(typeof logger.silent, "function");
|
|
54
|
+
assertEquals(typeof logger.child, "function");
|
|
55
|
+
assertEquals(typeof logger.level, "string");
|
|
56
|
+
} finally {
|
|
57
|
+
await cleanup();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("getLogTapeFastifyLogger(): uses default category ['fastify']", async () => {
|
|
62
|
+
const { logs, cleanup } = await setupLogtape();
|
|
63
|
+
try {
|
|
64
|
+
const logger = getLogTapeFastifyLogger();
|
|
65
|
+
logger.info("test message");
|
|
66
|
+
|
|
67
|
+
assertEquals(logs.length, 1);
|
|
68
|
+
assertEquals(logs[0].category, ["fastify"]);
|
|
69
|
+
} finally {
|
|
70
|
+
await cleanup();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("getLogTapeFastifyLogger(): uses custom category array", async () => {
|
|
75
|
+
const { logs, cleanup } = await setupLogtape();
|
|
76
|
+
try {
|
|
77
|
+
const logger = getLogTapeFastifyLogger({ category: ["myapp", "http"] });
|
|
78
|
+
logger.info("test message");
|
|
79
|
+
|
|
80
|
+
assertEquals(logs.length, 1);
|
|
81
|
+
assertEquals(logs[0].category, ["myapp", "http"]);
|
|
82
|
+
} finally {
|
|
83
|
+
await cleanup();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("getLogTapeFastifyLogger(): accepts string category", async () => {
|
|
88
|
+
const { logs, cleanup } = await setupLogtape();
|
|
89
|
+
try {
|
|
90
|
+
const logger = getLogTapeFastifyLogger({ category: "myapp" });
|
|
91
|
+
logger.info("test message");
|
|
92
|
+
|
|
93
|
+
assertEquals(logs.length, 1);
|
|
94
|
+
assertEquals(logs[0].category, ["myapp"]);
|
|
95
|
+
} finally {
|
|
96
|
+
await cleanup();
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ============================================
|
|
101
|
+
// Log Level Method Tests - String Messages
|
|
102
|
+
// ============================================
|
|
103
|
+
|
|
104
|
+
test("logger.info(): logs at info level with string message", async () => {
|
|
105
|
+
const { logs, cleanup } = await setupLogtape();
|
|
106
|
+
try {
|
|
107
|
+
const logger = getLogTapeFastifyLogger();
|
|
108
|
+
logger.info("Hello world");
|
|
109
|
+
|
|
110
|
+
assertEquals(logs.length, 1);
|
|
111
|
+
assertEquals(logs[0].level, "info");
|
|
112
|
+
assertEquals(logs[0].rawMessage, "Hello world");
|
|
113
|
+
} finally {
|
|
114
|
+
await cleanup();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("logger.error(): logs at error level", async () => {
|
|
119
|
+
const { logs, cleanup } = await setupLogtape();
|
|
120
|
+
try {
|
|
121
|
+
const logger = getLogTapeFastifyLogger();
|
|
122
|
+
logger.error("An error occurred");
|
|
123
|
+
|
|
124
|
+
assertEquals(logs.length, 1);
|
|
125
|
+
assertEquals(logs[0].level, "error");
|
|
126
|
+
assertEquals(logs[0].rawMessage, "An error occurred");
|
|
127
|
+
} finally {
|
|
128
|
+
await cleanup();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("logger.debug(): logs at debug level", async () => {
|
|
133
|
+
const { logs, cleanup } = await setupLogtape();
|
|
134
|
+
try {
|
|
135
|
+
const logger = getLogTapeFastifyLogger();
|
|
136
|
+
logger.debug("Debug message");
|
|
137
|
+
|
|
138
|
+
assertEquals(logs.length, 1);
|
|
139
|
+
assertEquals(logs[0].level, "debug");
|
|
140
|
+
assertEquals(logs[0].rawMessage, "Debug message");
|
|
141
|
+
} finally {
|
|
142
|
+
await cleanup();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("logger.warn(): logs at warning level", async () => {
|
|
147
|
+
const { logs, cleanup } = await setupLogtape();
|
|
148
|
+
try {
|
|
149
|
+
const logger = getLogTapeFastifyLogger();
|
|
150
|
+
logger.warn("Warning message");
|
|
151
|
+
|
|
152
|
+
assertEquals(logs.length, 1);
|
|
153
|
+
assertEquals(logs[0].level, "warning");
|
|
154
|
+
assertEquals(logs[0].rawMessage, "Warning message");
|
|
155
|
+
} finally {
|
|
156
|
+
await cleanup();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("logger.trace(): logs at trace level", async () => {
|
|
161
|
+
const { logs, cleanup } = await setupLogtape();
|
|
162
|
+
try {
|
|
163
|
+
const logger = getLogTapeFastifyLogger();
|
|
164
|
+
logger.trace("Trace message");
|
|
165
|
+
|
|
166
|
+
assertEquals(logs.length, 1);
|
|
167
|
+
assertEquals(logs[0].level, "trace");
|
|
168
|
+
assertEquals(logs[0].rawMessage, "Trace message");
|
|
169
|
+
} finally {
|
|
170
|
+
await cleanup();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("logger.fatal(): logs at fatal level", async () => {
|
|
175
|
+
const { logs, cleanup } = await setupLogtape();
|
|
176
|
+
try {
|
|
177
|
+
const logger = getLogTapeFastifyLogger();
|
|
178
|
+
logger.fatal("Fatal error");
|
|
179
|
+
|
|
180
|
+
assertEquals(logs.length, 1);
|
|
181
|
+
assertEquals(logs[0].level, "fatal");
|
|
182
|
+
assertEquals(logs[0].rawMessage, "Fatal error");
|
|
183
|
+
} finally {
|
|
184
|
+
await cleanup();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("logger.silent(): is a no-op", async () => {
|
|
189
|
+
const { logs, cleanup } = await setupLogtape();
|
|
190
|
+
try {
|
|
191
|
+
const logger = getLogTapeFastifyLogger();
|
|
192
|
+
logger.silent();
|
|
193
|
+
|
|
194
|
+
assertEquals(logs.length, 0);
|
|
195
|
+
} finally {
|
|
196
|
+
await cleanup();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// ============================================
|
|
201
|
+
// Object + Message Tests
|
|
202
|
+
// ============================================
|
|
203
|
+
|
|
204
|
+
test("logger.info(): logs with object and message", async () => {
|
|
205
|
+
const { logs, cleanup } = await setupLogtape();
|
|
206
|
+
try {
|
|
207
|
+
const logger = getLogTapeFastifyLogger();
|
|
208
|
+
logger.info({ userId: 123, action: "login" }, "User logged in");
|
|
209
|
+
|
|
210
|
+
assertEquals(logs.length, 1);
|
|
211
|
+
assertEquals(logs[0].level, "info");
|
|
212
|
+
assertEquals(logs[0].rawMessage, "User logged in");
|
|
213
|
+
assertEquals(logs[0].properties.userId, 123);
|
|
214
|
+
assertEquals(logs[0].properties.action, "login");
|
|
215
|
+
} finally {
|
|
216
|
+
await cleanup();
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("logger.info(): logs object-only with msg property", async () => {
|
|
221
|
+
const { logs, cleanup } = await setupLogtape();
|
|
222
|
+
try {
|
|
223
|
+
const logger = getLogTapeFastifyLogger();
|
|
224
|
+
logger.info({ msg: "Hello", data: 456 });
|
|
225
|
+
|
|
226
|
+
assertEquals(logs.length, 1);
|
|
227
|
+
assertEquals(logs[0].rawMessage, "Hello");
|
|
228
|
+
assertEquals(logs[0].properties.data, 456);
|
|
229
|
+
assertEquals("msg" in logs[0].properties, false);
|
|
230
|
+
} finally {
|
|
231
|
+
await cleanup();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("logger.info(): logs object-only without msg property", async () => {
|
|
236
|
+
const { logs, cleanup } = await setupLogtape();
|
|
237
|
+
try {
|
|
238
|
+
const logger = getLogTapeFastifyLogger();
|
|
239
|
+
logger.info({ key: "value", num: 789 });
|
|
240
|
+
|
|
241
|
+
assertEquals(logs.length, 1);
|
|
242
|
+
assertEquals(logs[0].rawMessage, "{*}");
|
|
243
|
+
assertEquals(logs[0].properties.key, "value");
|
|
244
|
+
assertEquals(logs[0].properties.num, 789);
|
|
245
|
+
} finally {
|
|
246
|
+
await cleanup();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ============================================
|
|
251
|
+
// Printf-style Interpolation Tests
|
|
252
|
+
// ============================================
|
|
253
|
+
|
|
254
|
+
test("logger.info(): supports printf-style %s interpolation", async () => {
|
|
255
|
+
const { logs, cleanup } = await setupLogtape();
|
|
256
|
+
try {
|
|
257
|
+
const logger = getLogTapeFastifyLogger();
|
|
258
|
+
logger.info("Hello %s", "world");
|
|
259
|
+
|
|
260
|
+
assertEquals(logs.length, 1);
|
|
261
|
+
assertEquals(logs[0].rawMessage, "Hello world");
|
|
262
|
+
} finally {
|
|
263
|
+
await cleanup();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("logger.info(): supports printf-style %d interpolation", async () => {
|
|
268
|
+
const { logs, cleanup } = await setupLogtape();
|
|
269
|
+
try {
|
|
270
|
+
const logger = getLogTapeFastifyLogger();
|
|
271
|
+
logger.info("Count: %d", 42);
|
|
272
|
+
|
|
273
|
+
assertEquals(logs.length, 1);
|
|
274
|
+
assertEquals(logs[0].rawMessage, "Count: 42");
|
|
275
|
+
} finally {
|
|
276
|
+
await cleanup();
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("logger.info(): supports printf-style %j interpolation", async () => {
|
|
281
|
+
const { logs, cleanup } = await setupLogtape();
|
|
282
|
+
try {
|
|
283
|
+
const logger = getLogTapeFastifyLogger();
|
|
284
|
+
logger.info("Data: %j", { key: "value" });
|
|
285
|
+
|
|
286
|
+
assertEquals(logs.length, 1);
|
|
287
|
+
assertEquals(logs[0].rawMessage, 'Data: {"key":"value"}');
|
|
288
|
+
} finally {
|
|
289
|
+
await cleanup();
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("logger.info(): supports printf-style %o interpolation", async () => {
|
|
294
|
+
const { logs, cleanup } = await setupLogtape();
|
|
295
|
+
try {
|
|
296
|
+
const logger = getLogTapeFastifyLogger();
|
|
297
|
+
logger.info("Object: %o", { a: 1 });
|
|
298
|
+
|
|
299
|
+
assertEquals(logs.length, 1);
|
|
300
|
+
assertEquals(logs[0].rawMessage, 'Object: {"a":1}');
|
|
301
|
+
} finally {
|
|
302
|
+
await cleanup();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("logger.info(): supports multiple interpolations", async () => {
|
|
307
|
+
const { logs, cleanup } = await setupLogtape();
|
|
308
|
+
try {
|
|
309
|
+
const logger = getLogTapeFastifyLogger();
|
|
310
|
+
logger.info("User %s performed %s %d times", "alice", "login", 3);
|
|
311
|
+
|
|
312
|
+
assertEquals(logs.length, 1);
|
|
313
|
+
assertEquals(logs[0].rawMessage, "User alice performed login 3 times");
|
|
314
|
+
} finally {
|
|
315
|
+
await cleanup();
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("logger.info(): handles escaped %%", async () => {
|
|
320
|
+
const { logs, cleanup } = await setupLogtape();
|
|
321
|
+
try {
|
|
322
|
+
const logger = getLogTapeFastifyLogger();
|
|
323
|
+
logger.info("100%% complete");
|
|
324
|
+
|
|
325
|
+
assertEquals(logs.length, 1);
|
|
326
|
+
assertEquals(logs[0].rawMessage, "100% complete");
|
|
327
|
+
} finally {
|
|
328
|
+
await cleanup();
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("logger.info(): handles missing interpolation args", async () => {
|
|
333
|
+
const { logs, cleanup } = await setupLogtape();
|
|
334
|
+
try {
|
|
335
|
+
const logger = getLogTapeFastifyLogger();
|
|
336
|
+
logger.info("Hello %s %s", "world");
|
|
337
|
+
|
|
338
|
+
assertEquals(logs.length, 1);
|
|
339
|
+
assertEquals(logs[0].rawMessage, "Hello world %s");
|
|
340
|
+
} finally {
|
|
341
|
+
await cleanup();
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// ============================================
|
|
346
|
+
// Child Logger Tests
|
|
347
|
+
// ============================================
|
|
348
|
+
|
|
349
|
+
test("logger.child(): creates a child logger with bindings", async () => {
|
|
350
|
+
const { logs, cleanup } = await setupLogtape();
|
|
351
|
+
try {
|
|
352
|
+
const logger = getLogTapeFastifyLogger();
|
|
353
|
+
const child = logger.child({ reqId: "abc123" });
|
|
354
|
+
|
|
355
|
+
child.info("Request received");
|
|
356
|
+
|
|
357
|
+
assertEquals(logs.length, 1);
|
|
358
|
+
assertEquals(logs[0].properties.reqId, "abc123");
|
|
359
|
+
} finally {
|
|
360
|
+
await cleanup();
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("logger.child(): returns a PinoLikeLogger", async () => {
|
|
365
|
+
const { cleanup } = await setupLogtape();
|
|
366
|
+
try {
|
|
367
|
+
const logger = getLogTapeFastifyLogger();
|
|
368
|
+
const child = logger.child({ reqId: "abc123" });
|
|
369
|
+
|
|
370
|
+
assertEquals(typeof child.info, "function");
|
|
371
|
+
assertEquals(typeof child.error, "function");
|
|
372
|
+
assertEquals(typeof child.child, "function");
|
|
373
|
+
assertEquals(typeof child.level, "string");
|
|
374
|
+
} finally {
|
|
375
|
+
await cleanup();
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("logger.child(): merges bindings with additional properties", async () => {
|
|
380
|
+
const { logs, cleanup } = await setupLogtape();
|
|
381
|
+
try {
|
|
382
|
+
const logger = getLogTapeFastifyLogger();
|
|
383
|
+
const child = logger.child({ reqId: "abc123" });
|
|
384
|
+
|
|
385
|
+
child.info({ action: "test" }, "Message");
|
|
386
|
+
|
|
387
|
+
assertEquals(logs.length, 1);
|
|
388
|
+
assertEquals(logs[0].properties.reqId, "abc123");
|
|
389
|
+
assertEquals(logs[0].properties.action, "test");
|
|
390
|
+
} finally {
|
|
391
|
+
await cleanup();
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("logger.child(): creates nested child loggers", async () => {
|
|
396
|
+
const { logs, cleanup } = await setupLogtape();
|
|
397
|
+
try {
|
|
398
|
+
const logger = getLogTapeFastifyLogger();
|
|
399
|
+
const child1 = logger.child({ reqId: "abc123" });
|
|
400
|
+
const child2 = child1.child({ userId: 456 });
|
|
401
|
+
|
|
402
|
+
child2.info("Nested log");
|
|
403
|
+
|
|
404
|
+
assertEquals(logs.length, 1);
|
|
405
|
+
assertEquals(logs[0].properties.reqId, "abc123");
|
|
406
|
+
assertEquals(logs[0].properties.userId, 456);
|
|
407
|
+
} finally {
|
|
408
|
+
await cleanup();
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("logger.child(): child can override parent bindings", async () => {
|
|
413
|
+
const { logs, cleanup } = await setupLogtape();
|
|
414
|
+
try {
|
|
415
|
+
const logger = getLogTapeFastifyLogger();
|
|
416
|
+
const parent = logger.child({ key: "parent" });
|
|
417
|
+
const child = parent.child({ key: "child" });
|
|
418
|
+
|
|
419
|
+
child.info("Test");
|
|
420
|
+
|
|
421
|
+
assertEquals(logs.length, 1);
|
|
422
|
+
assertEquals(logs[0].properties.key, "child");
|
|
423
|
+
} finally {
|
|
424
|
+
await cleanup();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("logger.child(): preserves parent category", async () => {
|
|
429
|
+
const { logs, cleanup } = await setupLogtape();
|
|
430
|
+
try {
|
|
431
|
+
const logger = getLogTapeFastifyLogger({ category: ["myapp"] });
|
|
432
|
+
const child = logger.child({ reqId: "123" });
|
|
433
|
+
|
|
434
|
+
child.info("Test");
|
|
435
|
+
|
|
436
|
+
assertEquals(logs.length, 1);
|
|
437
|
+
assertEquals(logs[0].category, ["myapp"]);
|
|
438
|
+
} finally {
|
|
439
|
+
await cleanup();
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// ============================================
|
|
444
|
+
// Level Property Tests
|
|
445
|
+
// ============================================
|
|
446
|
+
|
|
447
|
+
test("logger.level: has default level 'info'", async () => {
|
|
448
|
+
const { cleanup } = await setupLogtape();
|
|
449
|
+
try {
|
|
450
|
+
const logger = getLogTapeFastifyLogger();
|
|
451
|
+
assertEquals(logger.level, "info");
|
|
452
|
+
} finally {
|
|
453
|
+
await cleanup();
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test("logger.level: accepts custom initial level", async () => {
|
|
458
|
+
const { cleanup } = await setupLogtape();
|
|
459
|
+
try {
|
|
460
|
+
const logger = getLogTapeFastifyLogger({ level: "debug" });
|
|
461
|
+
assertEquals(logger.level, "debug");
|
|
462
|
+
} finally {
|
|
463
|
+
await cleanup();
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test("logger.level: is writable", async () => {
|
|
468
|
+
const { cleanup } = await setupLogtape();
|
|
469
|
+
try {
|
|
470
|
+
const logger = getLogTapeFastifyLogger();
|
|
471
|
+
logger.level = "error";
|
|
472
|
+
assertEquals(logger.level, "error");
|
|
473
|
+
} finally {
|
|
474
|
+
await cleanup();
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("logger.level: child inherits parent level", async () => {
|
|
479
|
+
const { cleanup } = await setupLogtape();
|
|
480
|
+
try {
|
|
481
|
+
const logger = getLogTapeFastifyLogger({ level: "debug" });
|
|
482
|
+
const child = logger.child({ reqId: "123" });
|
|
483
|
+
assertEquals(child.level, "debug");
|
|
484
|
+
} finally {
|
|
485
|
+
await cleanup();
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// ============================================
|
|
490
|
+
// Fastify-specific Request/Response Object Tests
|
|
491
|
+
// ============================================
|
|
492
|
+
|
|
493
|
+
test("logger handles serialized request object from Fastify", async () => {
|
|
494
|
+
const { logs, cleanup } = await setupLogtape();
|
|
495
|
+
try {
|
|
496
|
+
const logger = getLogTapeFastifyLogger();
|
|
497
|
+
const serializedReq = {
|
|
498
|
+
method: "GET",
|
|
499
|
+
url: "/api/users",
|
|
500
|
+
hostname: "localhost",
|
|
501
|
+
remoteAddress: "127.0.0.1",
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
logger.info({ req: serializedReq }, "incoming request");
|
|
505
|
+
|
|
506
|
+
assertEquals(logs.length, 1);
|
|
507
|
+
assertEquals(logs[0].properties.req, serializedReq);
|
|
508
|
+
assertEquals(logs[0].rawMessage, "incoming request");
|
|
509
|
+
} finally {
|
|
510
|
+
await cleanup();
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("logger handles serialized response object from Fastify", async () => {
|
|
515
|
+
const { logs, cleanup } = await setupLogtape();
|
|
516
|
+
try {
|
|
517
|
+
const logger = getLogTapeFastifyLogger();
|
|
518
|
+
const serializedRes = {
|
|
519
|
+
statusCode: 200,
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
logger.info({ res: serializedRes, responseTime: 42 }, "request completed");
|
|
523
|
+
|
|
524
|
+
assertEquals(logs.length, 1);
|
|
525
|
+
assertEquals(logs[0].properties.res, serializedRes);
|
|
526
|
+
assertEquals(logs[0].properties.responseTime, 42);
|
|
527
|
+
assertEquals(logs[0].rawMessage, "request completed");
|
|
528
|
+
} finally {
|
|
529
|
+
await cleanup();
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// ============================================
|
|
534
|
+
// Error Object Tests
|
|
535
|
+
// ============================================
|
|
536
|
+
|
|
537
|
+
test("logger.error(): handles error objects in properties", async () => {
|
|
538
|
+
const { logs, cleanup } = await setupLogtape();
|
|
539
|
+
try {
|
|
540
|
+
const logger = getLogTapeFastifyLogger();
|
|
541
|
+
const error = new Error("Something went wrong");
|
|
542
|
+
|
|
543
|
+
logger.error({ err: error }, "An error occurred");
|
|
544
|
+
|
|
545
|
+
assertEquals(logs.length, 1);
|
|
546
|
+
assertEquals(logs[0].level, "error");
|
|
547
|
+
assertEquals(logs[0].properties.err, error);
|
|
548
|
+
assertEquals(logs[0].rawMessage, "An error occurred");
|
|
549
|
+
} finally {
|
|
550
|
+
await cleanup();
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// ============================================
|
|
555
|
+
// Edge Cases
|
|
556
|
+
// ============================================
|
|
557
|
+
|
|
558
|
+
test("logger handles empty message", async () => {
|
|
559
|
+
const { logs, cleanup } = await setupLogtape();
|
|
560
|
+
try {
|
|
561
|
+
const logger = getLogTapeFastifyLogger();
|
|
562
|
+
logger.info("");
|
|
563
|
+
|
|
564
|
+
assertEquals(logs.length, 1);
|
|
565
|
+
assertEquals(logs[0].rawMessage, "");
|
|
566
|
+
} finally {
|
|
567
|
+
await cleanup();
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("logger handles empty object", async () => {
|
|
572
|
+
const { logs, cleanup } = await setupLogtape();
|
|
573
|
+
try {
|
|
574
|
+
const logger = getLogTapeFastifyLogger();
|
|
575
|
+
logger.info({});
|
|
576
|
+
|
|
577
|
+
assertEquals(logs.length, 1);
|
|
578
|
+
assertEquals(logs[0].rawMessage, "{*}");
|
|
579
|
+
} finally {
|
|
580
|
+
await cleanup();
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("logger handles null-ish values in interpolation", async () => {
|
|
585
|
+
const { logs, cleanup } = await setupLogtape();
|
|
586
|
+
try {
|
|
587
|
+
const logger = getLogTapeFastifyLogger();
|
|
588
|
+
logger.info("Value: %s", null);
|
|
589
|
+
|
|
590
|
+
assertEquals(logs.length, 1);
|
|
591
|
+
assertEquals(logs[0].rawMessage, "Value: null");
|
|
592
|
+
} finally {
|
|
593
|
+
await cleanup();
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("logger handles undefined in interpolation", async () => {
|
|
598
|
+
const { logs, cleanup } = await setupLogtape();
|
|
599
|
+
try {
|
|
600
|
+
const logger = getLogTapeFastifyLogger();
|
|
601
|
+
logger.info("Value: %s", undefined);
|
|
602
|
+
|
|
603
|
+
assertEquals(logs.length, 1);
|
|
604
|
+
assertEquals(logs[0].rawMessage, "Value: undefined");
|
|
605
|
+
} finally {
|
|
606
|
+
await cleanup();
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// ============================================
|
|
611
|
+
// Integration Tests with Fastify
|
|
612
|
+
// ============================================
|
|
613
|
+
|
|
614
|
+
// Note: Fastify uses internal timers, so we need to disable sanitizers for integration tests
|
|
615
|
+
const sanitizerOptions = {
|
|
616
|
+
sanitizeOps: false,
|
|
617
|
+
sanitizeResources: false,
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
test(
|
|
621
|
+
"integration: Fastify server logs requests",
|
|
622
|
+
sanitizerOptions,
|
|
623
|
+
async () => {
|
|
624
|
+
const { default: Fastify } = await import("fastify");
|
|
625
|
+
const { logs, cleanup } = await setupLogtape();
|
|
626
|
+
try {
|
|
627
|
+
const fastify = Fastify({
|
|
628
|
+
loggerInstance: getLogTapeFastifyLogger({
|
|
629
|
+
category: ["fastify", "test"],
|
|
630
|
+
}),
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
fastify.get("/", (_request, _reply) => {
|
|
634
|
+
return { hello: "world" };
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
await fastify.ready();
|
|
638
|
+
|
|
639
|
+
// Make a request using inject (doesn't require actual server)
|
|
640
|
+
const response = await fastify.inject({
|
|
641
|
+
method: "GET",
|
|
642
|
+
url: "/",
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
assertEquals(response.statusCode, 200);
|
|
646
|
+
assertEquals(JSON.parse(response.body), { hello: "world" });
|
|
647
|
+
|
|
648
|
+
// Fastify should have logged server ready message
|
|
649
|
+
const serverLogs = logs.filter((log) =>
|
|
650
|
+
log.category[0] === "fastify" && log.category[1] === "test"
|
|
651
|
+
);
|
|
652
|
+
assertEquals(serverLogs.length > 0, true);
|
|
653
|
+
|
|
654
|
+
await fastify.close();
|
|
655
|
+
} finally {
|
|
656
|
+
await cleanup();
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
test(
|
|
662
|
+
"integration: Fastify request.log creates child logger",
|
|
663
|
+
sanitizerOptions,
|
|
664
|
+
async () => {
|
|
665
|
+
const { default: Fastify } = await import("fastify");
|
|
666
|
+
const { logs, cleanup } = await setupLogtape();
|
|
667
|
+
try {
|
|
668
|
+
const fastify = Fastify({
|
|
669
|
+
loggerInstance: getLogTapeFastifyLogger({ category: ["fastify"] }),
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
fastify.get("/test", (request, _reply) => {
|
|
673
|
+
request.log.info({ customProp: "test-value" }, "Request handler log");
|
|
674
|
+
return { ok: true };
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
await fastify.ready();
|
|
678
|
+
|
|
679
|
+
const response = await fastify.inject({
|
|
680
|
+
method: "GET",
|
|
681
|
+
url: "/test",
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
assertEquals(response.statusCode, 200);
|
|
685
|
+
|
|
686
|
+
// Find the log from our handler
|
|
687
|
+
const handlerLog = logs.find((log) =>
|
|
688
|
+
log.rawMessage === "Request handler log"
|
|
689
|
+
);
|
|
690
|
+
assertEquals(handlerLog !== undefined, true);
|
|
691
|
+
assertEquals(handlerLog?.properties.customProp, "test-value");
|
|
692
|
+
// Fastify adds reqId to child logger bindings
|
|
693
|
+
assertEquals("reqId" in (handlerLog?.properties ?? {}), true);
|
|
694
|
+
|
|
695
|
+
await fastify.close();
|
|
696
|
+
} finally {
|
|
697
|
+
await cleanup();
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
test(
|
|
703
|
+
"integration: Fastify logs at different levels",
|
|
704
|
+
sanitizerOptions,
|
|
705
|
+
async () => {
|
|
706
|
+
const { default: Fastify } = await import("fastify");
|
|
707
|
+
const { logs, cleanup } = await setupLogtape();
|
|
708
|
+
try {
|
|
709
|
+
const fastify = Fastify({
|
|
710
|
+
loggerInstance: getLogTapeFastifyLogger(),
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
fastify.get("/levels", (request, _reply) => {
|
|
714
|
+
request.log.debug("Debug message");
|
|
715
|
+
request.log.info("Info message");
|
|
716
|
+
request.log.warn("Warn message");
|
|
717
|
+
request.log.error("Error message");
|
|
718
|
+
return { ok: true };
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
await fastify.ready();
|
|
722
|
+
|
|
723
|
+
await fastify.inject({
|
|
724
|
+
method: "GET",
|
|
725
|
+
url: "/levels",
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
const debugLog = logs.find((log) => log.rawMessage === "Debug message");
|
|
729
|
+
const infoLog = logs.find((log) => log.rawMessage === "Info message");
|
|
730
|
+
const warnLog = logs.find((log) => log.rawMessage === "Warn message");
|
|
731
|
+
const errorLog = logs.find((log) => log.rawMessage === "Error message");
|
|
732
|
+
|
|
733
|
+
assertEquals(debugLog?.level, "debug");
|
|
734
|
+
assertEquals(infoLog?.level, "info");
|
|
735
|
+
assertEquals(warnLog?.level, "warning");
|
|
736
|
+
assertEquals(errorLog?.level, "error");
|
|
737
|
+
|
|
738
|
+
await fastify.close();
|
|
739
|
+
} finally {
|
|
740
|
+
await cleanup();
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
test(
|
|
746
|
+
"integration: Fastify logs with object properties",
|
|
747
|
+
sanitizerOptions,
|
|
748
|
+
async () => {
|
|
749
|
+
const { default: Fastify } = await import("fastify");
|
|
750
|
+
const { logs, cleanup } = await setupLogtape();
|
|
751
|
+
try {
|
|
752
|
+
const fastify = Fastify({
|
|
753
|
+
loggerInstance: getLogTapeFastifyLogger(),
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
fastify.get("/props", (request, _reply) => {
|
|
757
|
+
request.log.info({ userId: 123, action: "test" }, "Action performed");
|
|
758
|
+
return { ok: true };
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
await fastify.ready();
|
|
762
|
+
|
|
763
|
+
await fastify.inject({
|
|
764
|
+
method: "GET",
|
|
765
|
+
url: "/props",
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const actionLog = logs.find((log) =>
|
|
769
|
+
log.rawMessage === "Action performed"
|
|
770
|
+
);
|
|
771
|
+
assertEquals(actionLog?.properties.userId, 123);
|
|
772
|
+
assertEquals(actionLog?.properties.action, "test");
|
|
773
|
+
|
|
774
|
+
await fastify.close();
|
|
775
|
+
} finally {
|
|
776
|
+
await cleanup();
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
);
|