@vvlad1973/simple-logger 2.1.10 → 2.2.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.
@@ -1,81 +1,511 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
1
  import SimpleLogger from '../classes/simple-logger.js';
3
- import type { ExternalLogger } from '../types/simple-logger.types.js';
2
+ import type { ExternalLogger, LoggerFactory } from '../types/simple-logger.types.js';
4
3
 
5
4
  describe('SimpleLogger', () => {
6
5
  let consoleSpy: ReturnType<typeof vi.spyOn>;
6
+ let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
7
7
 
8
8
  beforeEach(() => {
9
9
  consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
10
+ consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
10
11
  });
11
12
 
12
13
  afterEach(() => {
13
14
  consoleSpy.mockRestore();
15
+ consoleWarnSpy.mockRestore();
14
16
  });
15
17
 
16
- it('should log using console when no external logger is provided', () => {
17
- const logger = new SimpleLogger();
18
- logger.info('test message');
19
- expect(consoleSpy).toHaveBeenCalledOnce();
20
- expect(consoleSpy.mock.calls[0][0]).toMatch(/test message/);
18
+ describe('Initialization Cases', () => {
19
+ it('CASE 1: should use console when no external logger is provided', () => {
20
+ const logger = new SimpleLogger();
21
+ expect(logger.isExternal).toBe(false);
22
+ logger.info('test message');
23
+ expect(consoleSpy).toHaveBeenCalledOnce();
24
+ expect(consoleSpy.mock.calls[0][0]).toMatch(/test message/);
25
+ });
26
+
27
+ it('CASE 2: should call logger factory with options', () => {
28
+ const mockLogger: ExternalLogger = {
29
+ info: vi.fn(),
30
+ level: 'info',
31
+ };
32
+ const factory: LoggerFactory = vi.fn(() => mockLogger);
33
+
34
+ const logger = new SimpleLogger(factory, { level: 'debug', msgPrefix: 'TEST' });
35
+
36
+ expect(factory).toHaveBeenCalledWith({
37
+ level: 'debug',
38
+ msgPrefix: 'TEST',
39
+ });
40
+ expect(logger.isExternal).toBe(true);
41
+ });
42
+
43
+ it('CASE 3: should fallback to console for invalid external logger', () => {
44
+ const logger = new SimpleLogger({} as ExternalLogger);
45
+ expect(logger.isExternal).toBe(false);
46
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
47
+ 'Invalid or console logger passed, falling back to internal console.'
48
+ );
49
+ });
50
+
51
+ it('CASE 3b: should fallback to console when console is passed', () => {
52
+ const logger = new SimpleLogger(console as unknown as ExternalLogger);
53
+ expect(logger.isExternal).toBe(false);
54
+ });
55
+
56
+ it('CASE 4: should use external logger without params', () => {
57
+ const external: ExternalLogger = {
58
+ info: vi.fn(),
59
+ warn: vi.fn(),
60
+ level: 'info',
61
+ };
62
+ const logger = new SimpleLogger(external);
63
+ expect(logger.isExternal).toBe(true);
64
+
65
+ logger.info('test');
66
+ expect(external.info).toHaveBeenCalledWith('test');
67
+ });
68
+
69
+ it('CASE 5: should create child logger when external has .child() method', () => {
70
+ const childLogger: ExternalLogger = {
71
+ info: vi.fn(),
72
+ level: 'info',
73
+ };
74
+ const childFn = vi.fn(() => childLogger);
75
+ const external: ExternalLogger = {
76
+ info: vi.fn(),
77
+ level: 'info',
78
+ child: childFn,
79
+ };
80
+
81
+ const logger = new SimpleLogger(external, {
82
+ level: 'debug',
83
+ bindings: { app: 'test' },
84
+ msgPrefix: 'PREFIX',
85
+ });
86
+
87
+ expect(childFn).toHaveBeenCalledWith(
88
+ { app: 'test' },
89
+ { level: 'debug', msgPrefix: 'PREFIX' }
90
+ );
91
+ expect(logger.isExternal).toBe(true);
92
+ });
93
+
94
+ it('CASE 6: should use external logger directly without .child() and set level', () => {
95
+ const external: ExternalLogger = {
96
+ info: vi.fn(),
97
+ level: 'info',
98
+ };
99
+
100
+ const logger = new SimpleLogger(external, { level: 'debug' });
101
+
102
+ expect(external.level).toBe('debug');
103
+ expect(logger.isExternal).toBe(true);
104
+ });
105
+ });
106
+
107
+ describe('Logging Methods', () => {
108
+ it('should log at all levels', () => {
109
+ const logger = new SimpleLogger(null, { level: 'trace' });
110
+
111
+ logger.trace('trace msg');
112
+ logger.debug('debug msg');
113
+ logger.info('info msg');
114
+ logger.warn('warn msg');
115
+ logger.error('error msg');
116
+ logger.fatal('fatal msg');
117
+
118
+ expect(consoleSpy).toHaveBeenCalledTimes(6);
119
+ });
120
+
121
+ it('should not log below current level', () => {
122
+ const logger = new SimpleLogger(null, { level: 'warn' });
123
+
124
+ logger.trace('should not log');
125
+ logger.debug('should not log');
126
+ logger.info('should not log');
127
+ logger.warn('should log');
128
+ logger.error('should log');
129
+
130
+ expect(consoleSpy).toHaveBeenCalledTimes(2);
131
+ });
132
+
133
+ it('should respect msgPrefix in console output', () => {
134
+ const logger = new SimpleLogger(null, { msgPrefix: '[PREFIX]' });
135
+ logger.info('message');
136
+ expect(consoleSpy.mock.calls[0][0]).toMatch(/\[PREFIX\]/);
137
+ });
138
+
139
+ it('should include bindings in console output', () => {
140
+ const logger = new SimpleLogger(null, { bindings: { user: 'john', id: 123 } });
141
+ logger.info('message');
142
+ const output = consoleSpy.mock.calls[0][0];
143
+ expect(output).toMatch(/user=john/);
144
+ expect(output).toMatch(/id=123/);
145
+ });
146
+
147
+ it('should handle object context with message', () => {
148
+ const logger = new SimpleLogger();
149
+ logger.info({ userId: 123 }, 'user action');
150
+ const output = consoleSpy.mock.calls[0][0];
151
+ expect(output).toMatch(/userId=123/);
152
+ expect(output).toMatch(/user action/);
153
+ });
154
+
155
+ it('should handle message with format arguments', () => {
156
+ const logger = new SimpleLogger();
157
+ logger.info('Hello %s, you are %d years old', 'John', 30);
158
+ const output = consoleSpy.mock.calls[0][0];
159
+ expect(output).toMatch(/Hello John, you are 30 years old/);
160
+ });
161
+
162
+ it('should handle object-only logging', () => {
163
+ const logger = new SimpleLogger();
164
+ logger.info({ status: 'ok' });
165
+ expect(consoleSpy).toHaveBeenCalled();
166
+ });
167
+ });
168
+
169
+ describe('External Logger Integration', () => {
170
+ it('should call external logger with string message', () => {
171
+ const external: ExternalLogger = {
172
+ info: vi.fn(),
173
+ level: 'info',
174
+ };
175
+ const logger = new SimpleLogger(external);
176
+
177
+ logger.info('test message');
178
+
179
+ expect(external.info).toHaveBeenCalledWith('test message');
180
+ });
181
+
182
+ it('should call external logger with object and message', () => {
183
+ const external: ExternalLogger = {
184
+ info: vi.fn(),
185
+ level: 'info',
186
+ };
187
+ const logger = new SimpleLogger(external);
188
+
189
+ logger.info({ userId: 123 }, 'user action');
190
+
191
+ expect(external.info).toHaveBeenCalledWith({ userId: 123 }, 'user action');
192
+ });
193
+
194
+ it('should call external logger with object only', () => {
195
+ const external: ExternalLogger = {
196
+ info: vi.fn(),
197
+ level: 'info',
198
+ };
199
+ const logger = new SimpleLogger(external);
200
+
201
+ logger.info({ status: 'ok' });
202
+
203
+ expect(external.info).toHaveBeenCalledWith({ status: 'ok' }, undefined);
204
+ });
205
+
206
+ it('should call external logger with message and format args', () => {
207
+ const external: ExternalLogger = {
208
+ info: vi.fn(),
209
+ level: 'info',
210
+ };
211
+ const logger = new SimpleLogger(external);
212
+
213
+ logger.info('Hello %s', 'world');
214
+
215
+ expect(external.info).toHaveBeenCalledWith('Hello %s', 'world');
216
+ });
217
+
218
+ it('should skip logging if method does not exist on external logger', () => {
219
+ const external: ExternalLogger = {
220
+ info: vi.fn(),
221
+ level: 'info',
222
+ };
223
+ const logger = new SimpleLogger(external);
224
+
225
+ logger.debug('should not call');
226
+
227
+ expect(external.info).not.toHaveBeenCalled();
228
+ });
229
+
230
+ it('should warn on invalid log arguments', () => {
231
+ const external: ExternalLogger = {
232
+ info: vi.fn(),
233
+ level: 'info',
234
+ };
235
+ const logger = new SimpleLogger(external);
236
+
237
+ // Simulate invalid prepared args by calling with invalid types
238
+ (logger as any).callExternalLogMethod('info', [undefined]);
239
+
240
+ expect(consoleWarnSpy).toHaveBeenCalled();
241
+ });
242
+ });
243
+
244
+ describe('Level Management', () => {
245
+ it('should get current level', () => {
246
+ const logger = new SimpleLogger(null, { level: 'warn' });
247
+ expect(logger.getLevel()).toBe('warn');
248
+ });
249
+
250
+ it('should set new level', () => {
251
+ const logger = new SimpleLogger(null, { level: 'info' });
252
+ expect(logger.getLevel()).toBe('info');
253
+
254
+ logger.setLevel('error');
255
+ expect(logger.getLevel()).toBe('error');
256
+
257
+ logger.info('should not log');
258
+ logger.error('should log');
259
+
260
+ expect(consoleSpy).toHaveBeenCalledOnce();
261
+ });
262
+
263
+ it('should sync level with external logger', () => {
264
+ const external: ExternalLogger = {
265
+ info: vi.fn(),
266
+ level: 'info',
267
+ };
268
+ const logger = new SimpleLogger(external);
269
+
270
+ logger.setLevel('debug');
271
+
272
+ expect(external.level).toBe('debug');
273
+ });
274
+
275
+ it('should resolve level from external logger', () => {
276
+ const external: ExternalLogger = {
277
+ info: vi.fn(),
278
+ level: 'warn',
279
+ };
280
+ const logger = new SimpleLogger(external);
281
+
282
+ expect(logger.getLevel()).toBe('warn');
283
+ });
284
+
285
+ it('should prioritize explicit level over logger level', () => {
286
+ const external: ExternalLogger = {
287
+ info: vi.fn(),
288
+ level: 'warn',
289
+ };
290
+ const logger = new SimpleLogger(external, { level: 'debug' });
291
+
292
+ expect(logger.getLevel()).toBe('debug');
293
+ });
294
+
295
+ it('should ignore invalid level in external logger', () => {
296
+ const external: ExternalLogger = {
297
+ info: vi.fn(),
298
+ level: 'invalid_level' as any,
299
+ };
300
+ const logger = new SimpleLogger(external);
301
+
302
+ // Should fall back to default level
303
+ expect(logger.getLevel()).toBe('info');
304
+ });
21
305
  });
22
306
 
23
- it('should not log below current level', () => {
24
- const logger = new SimpleLogger(null, { level: 'warn' });
25
- logger.info('should not log');
26
- logger.warn('should log');
27
- expect(consoleSpy).toHaveBeenCalledOnce();
28
- expect(consoleSpy.mock.calls[0][0]).toMatch(/should log/);
307
+ describe('Enable/Disable', () => {
308
+ it('should disable all logging when disabled', () => {
309
+ const logger = new SimpleLogger(null, { level: 'trace' });
310
+
311
+ logger.disable();
312
+
313
+ logger.trace('should not log');
314
+ logger.info('should not log');
315
+ logger.error('should not log');
316
+
317
+ expect(consoleSpy).not.toHaveBeenCalled();
318
+ });
319
+
320
+ it('should enable logging when re-enabled', () => {
321
+ const logger = new SimpleLogger(null, { level: 'info' });
322
+
323
+ logger.disable();
324
+ logger.info('should not log');
325
+
326
+ logger.enable();
327
+ logger.info('should log');
328
+
329
+ expect(consoleSpy).toHaveBeenCalledOnce();
330
+ });
29
331
  });
30
332
 
31
- it('should respect msgPrefix', () => {
32
- const logger = new SimpleLogger(null, { msgPrefix: '[PREFIX]' });
33
- logger.info('message');
34
- expect(consoleSpy.mock.calls[0][0]).toMatch(/\[PREFIX\]/);
333
+ describe('Child Logger', () => {
334
+ it('should create child logger with merged bindings', () => {
335
+ const logger = new SimpleLogger(null, { bindings: { user: 'A' } });
336
+ const child = logger.child({ request: '123' });
337
+
338
+ child.info('child message');
339
+
340
+ const output = consoleSpy.mock.calls[0][0];
341
+ expect(output).toMatch(/user=A/);
342
+ expect(output).toMatch(/request=123/);
343
+ expect(output).toMatch(/child message/);
344
+ });
345
+
346
+ it('should call external logger .child() method if available', () => {
347
+ const childLogger: ExternalLogger = {
348
+ info: vi.fn(),
349
+ level: 'info',
350
+ };
351
+ const childFn = vi.fn(() => childLogger);
352
+ const external: ExternalLogger = {
353
+ level: 'info',
354
+ child: childFn,
355
+ info: vi.fn(),
356
+ };
357
+
358
+ const logger = new SimpleLogger(external);
359
+ const child = logger.child({ traceId: 'xyz' });
360
+
361
+ child.info('from child');
362
+
363
+ expect(childFn).toHaveBeenCalledWith({ traceId: 'xyz' });
364
+ expect(childLogger.info).toHaveBeenCalledWith('from child');
365
+ });
366
+
367
+ it('should create child without calling external .child() if not function', () => {
368
+ const external: ExternalLogger = {
369
+ info: vi.fn(),
370
+ level: 'info',
371
+ };
372
+
373
+ const logger = new SimpleLogger(external);
374
+ const child = logger.child({ req: '456' });
375
+
376
+ expect(child).toBeInstanceOf(SimpleLogger);
377
+ });
35
378
  });
36
379
 
37
- it('should use external logger methods if provided', () => {
38
- const external: ExternalLogger = {
39
- info: vi.fn(),
40
- level: 'info',
41
- };
42
- const logger = new SimpleLogger(external);
43
- logger.info('external log');
44
- expect(external.info).toHaveBeenCalledOnce();
45
- expect(external.info).toHaveBeenCalledWith('external log');
380
+ describe('Function Logging', () => {
381
+ it('should log function start', () => {
382
+ const logger = new SimpleLogger(null, { level: 'trace' });
383
+
384
+ logger.logFunctionStart('myFunction');
385
+
386
+ expect(consoleSpy).toHaveBeenCalled();
387
+ expect(consoleSpy.mock.calls[0][0]).toMatch(/Function start: myFunction/);
388
+ });
389
+
390
+ it('should log function end', () => {
391
+ const logger = new SimpleLogger(null, { level: 'trace' });
392
+
393
+ logger.logFunctionEnd('myFunction');
394
+
395
+ expect(consoleSpy).toHaveBeenCalled();
396
+ expect(consoleSpy.mock.calls[0][0]).toMatch(/Function end: myFunction/);
397
+ });
398
+
399
+ it('should use caller name if not provided', () => {
400
+ const logger = new SimpleLogger(null, { level: 'trace' });
401
+
402
+ logger.logFunctionStart();
403
+ logger.logFunctionEnd();
404
+
405
+ expect(consoleSpy).toHaveBeenCalledTimes(2);
406
+ });
407
+ });
408
+
409
+ describe('Flush Method', () => {
410
+ it('should return immediately if not external logger', async () => {
411
+ const logger = new SimpleLogger();
412
+
413
+ await expect(logger.flush()).resolves.toBeUndefined();
414
+ });
415
+
416
+ it('should call flush on external logger if available', async () => {
417
+ const flushFn = vi.fn((cb: (err?: Error) => void) => cb());
418
+ const external: ExternalLogger & { flush: typeof flushFn } = {
419
+ info: vi.fn(),
420
+ level: 'info',
421
+ flush: flushFn,
422
+ };
423
+
424
+ const logger = new SimpleLogger(external);
425
+
426
+ await logger.flush();
427
+
428
+ expect(flushFn).toHaveBeenCalled();
429
+ });
430
+
431
+ it('should handle flush errors gracefully', async () => {
432
+ const flushFn = vi.fn((cb: (err?: Error) => void) => cb(new Error('Flush error')));
433
+ const external: ExternalLogger & { flush: typeof flushFn } = {
434
+ info: vi.fn(),
435
+ level: 'info',
436
+ flush: flushFn,
437
+ };
438
+
439
+ const logger = new SimpleLogger(external);
440
+
441
+ await expect(logger.flush()).resolves.toBeUndefined();
442
+ });
443
+
444
+ it('should return immediately if flush is not a function', async () => {
445
+ const external: ExternalLogger = {
446
+ info: vi.fn(),
447
+ level: 'info',
448
+ };
449
+
450
+ const logger = new SimpleLogger(external);
451
+
452
+ await expect(logger.flush()).resolves.toBeUndefined();
453
+ });
46
454
  });
47
455
 
48
- it('should fallback to console for invalid external logger', () => {
49
- const logger = new SimpleLogger({} as ExternalLogger);
50
- logger.info('fallback');
51
- expect(consoleSpy).toHaveBeenCalled();
456
+ describe('Silent Level', () => {
457
+ it('should not log anything at silent level', () => {
458
+ const logger = new SimpleLogger(null, { level: 'silent' });
459
+
460
+ logger.trace('should not log');
461
+ logger.debug('should not log');
462
+ logger.info('should not log');
463
+ logger.warn('should not log');
464
+ logger.error('should not log');
465
+ logger.fatal('should not log');
466
+
467
+ expect(consoleSpy).not.toHaveBeenCalled();
468
+ });
52
469
  });
53
470
 
54
- it('should create child logger with merged bindings', () => {
55
- const logger = new SimpleLogger(null, { bindings: { user: 'A' } });
56
- const child = logger.child({ request: '123' });
57
-
58
- child.info('child message');
59
-
60
- expect(consoleSpy.mock.calls.length).toBe(1);
61
- const output = consoleSpy.mock.calls[0][0];
62
-
63
- expect(output).toMatch(/user=A/);
64
- expect(output).toMatch(/request=123/);
65
- expect(output).toMatch(/child message/);
471
+ describe('Level Resolution', () => {
472
+ it('should handle logger without level property', () => {
473
+ const external: ExternalLogger = {
474
+ info: vi.fn(),
475
+ };
476
+
477
+ const logger = new SimpleLogger(external);
478
+
479
+ expect(logger.getLevel()).toBe('info');
480
+ });
481
+
482
+ it('should not update level on external logger without level property', () => {
483
+ const external: ExternalLogger = {
484
+ info: vi.fn(),
485
+ };
486
+
487
+ const logger = new SimpleLogger(external);
488
+ logger.setLevel('debug');
489
+
490
+ expect(external.level).toBeUndefined();
491
+ });
66
492
  });
67
-
68
-
69
-
70
- it('should call .child if external logger supports it', () => {
71
- const childFn = vi.fn(() => ({
72
- info: vi.fn(),
73
- level: 'info',
74
- }));
75
- const external = { level: 'info', child: childFn, info: vi.fn() };
76
- const logger = new SimpleLogger(external);
77
- const child = logger.child({ traceId: 'xyz' });
78
- child.info('from child');
79
- expect(childFn).toHaveBeenCalledWith({ traceId: 'xyz' });
493
+
494
+ describe('Factory with Transport', () => {
495
+ it('should pass transport option to factory', () => {
496
+ const mockLogger: ExternalLogger = {
497
+ info: vi.fn(),
498
+ level: 'info',
499
+ };
500
+ const factory: LoggerFactory = vi.fn(() => mockLogger);
501
+ const transport = { target: 'pino/file' };
502
+
503
+ new SimpleLogger(factory, { level: 'info', transport });
504
+
505
+ expect(factory).toHaveBeenCalledWith({
506
+ level: 'info',
507
+ transport,
508
+ });
509
+ });
80
510
  });
81
511
  });