@geekmidas/logger 0.0.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.
@@ -0,0 +1,526 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { ConsoleLogger } from '../console';
3
+
4
+ describe('ConsoleLogger', () => {
5
+ // Mock console methods
6
+ const originalConsole = {
7
+ debug: console.debug,
8
+ info: console.info,
9
+ warn: console.warn,
10
+ error: console.error,
11
+ trace: console.trace,
12
+ };
13
+
14
+ beforeEach(() => {
15
+ // Mock all console methods
16
+ console.debug = vi.fn();
17
+ console.info = vi.fn();
18
+ console.warn = vi.fn();
19
+ console.error = vi.fn();
20
+ console.trace = vi.fn();
21
+
22
+ // Mock Date.now for predictable timestamps
23
+ vi.spyOn(Date, 'now').mockReturnValue(1234567890);
24
+ });
25
+
26
+ afterEach(() => {
27
+ // Restore console methods
28
+ console.debug = originalConsole.debug;
29
+ console.info = originalConsole.info;
30
+ console.warn = originalConsole.warn;
31
+ console.error = originalConsole.error;
32
+ console.trace = originalConsole.trace;
33
+
34
+ vi.restoreAllMocks();
35
+ });
36
+
37
+ describe('Constructor', () => {
38
+ it('should create logger with no initial context', () => {
39
+ const logger = new ConsoleLogger();
40
+
41
+ expect(logger.data).toEqual({});
42
+ });
43
+
44
+ it('should create logger with initial context', () => {
45
+ const logger = new ConsoleLogger({ app: 'myApp', version: '1.0.0' });
46
+
47
+ expect(logger.data).toEqual({ app: 'myApp', version: '1.0.0' });
48
+ });
49
+ });
50
+
51
+ describe('Log levels', () => {
52
+ describe('debug', () => {
53
+ it('should log debug message with context', () => {
54
+ const logger = new ConsoleLogger({ app: 'test' });
55
+
56
+ logger.debug({ userId: 123 }, 'Debug message');
57
+
58
+ expect(console.debug).toHaveBeenCalledWith(
59
+ { app: 'test', userId: 123, ts: 1234567890 },
60
+ 'Debug message',
61
+ );
62
+ });
63
+
64
+ it('should log debug with only context object', () => {
65
+ const logger = new ConsoleLogger();
66
+
67
+ logger.debug({ action: 'test' });
68
+
69
+ expect(console.debug).toHaveBeenCalledWith({
70
+ action: 'test',
71
+ ts: 1234567890,
72
+ });
73
+ });
74
+ });
75
+
76
+ describe('info', () => {
77
+ it('should log info message with context', () => {
78
+ const logger = new ConsoleLogger({ app: 'test' });
79
+
80
+ logger.info({ userId: 123 }, 'Info message');
81
+
82
+ expect(console.info).toHaveBeenCalledWith(
83
+ { app: 'test', userId: 123, ts: 1234567890 },
84
+ 'Info message',
85
+ );
86
+ });
87
+
88
+ it('should log info with only context object', () => {
89
+ const logger = new ConsoleLogger();
90
+
91
+ logger.info({ status: 'success' });
92
+
93
+ expect(console.info).toHaveBeenCalledWith({
94
+ status: 'success',
95
+ ts: 1234567890,
96
+ });
97
+ });
98
+ });
99
+
100
+ describe('warn', () => {
101
+ it('should log warning message with context', () => {
102
+ const logger = new ConsoleLogger({ app: 'test' });
103
+
104
+ logger.warn({ code: 'DEPRECATED' }, 'Warning message');
105
+
106
+ expect(console.warn).toHaveBeenCalledWith(
107
+ { app: 'test', code: 'DEPRECATED', ts: 1234567890 },
108
+ 'Warning message',
109
+ );
110
+ });
111
+
112
+ it('should log warning with only context object', () => {
113
+ const logger = new ConsoleLogger();
114
+
115
+ logger.warn({ issue: 'memory' });
116
+
117
+ expect(console.warn).toHaveBeenCalledWith({
118
+ issue: 'memory',
119
+ ts: 1234567890,
120
+ });
121
+ });
122
+ });
123
+
124
+ describe('error', () => {
125
+ it('should log error message with context', () => {
126
+ const logger = new ConsoleLogger({ app: 'test' });
127
+ const error = new Error('Test error');
128
+
129
+ logger.error({ error }, 'Error occurred');
130
+
131
+ expect(console.error).toHaveBeenCalledWith(
132
+ { app: 'test', error, ts: 1234567890 },
133
+ 'Error occurred',
134
+ );
135
+ });
136
+
137
+ it('should log error with only context object', () => {
138
+ const logger = new ConsoleLogger();
139
+ const error = new Error('Test error');
140
+
141
+ logger.error({ error });
142
+
143
+ expect(console.error).toHaveBeenCalledWith({
144
+ error,
145
+ ts: 1234567890,
146
+ });
147
+ });
148
+ });
149
+
150
+ describe('fatal', () => {
151
+ it('should log fatal message with context', () => {
152
+ const logger = new ConsoleLogger({ app: 'test' });
153
+
154
+ logger.fatal({ exitCode: 1 }, 'Fatal error');
155
+
156
+ expect(console.error).toHaveBeenCalledWith(
157
+ { app: 'test', exitCode: 1, ts: 1234567890 },
158
+ 'Fatal error',
159
+ );
160
+ });
161
+
162
+ it('should log fatal with only context object', () => {
163
+ const logger = new ConsoleLogger();
164
+
165
+ logger.fatal({ critical: true });
166
+
167
+ expect(console.error).toHaveBeenCalledWith({
168
+ critical: true,
169
+ ts: 1234567890,
170
+ });
171
+ });
172
+ });
173
+
174
+ describe('trace', () => {
175
+ it('should log trace message with context', () => {
176
+ const logger = new ConsoleLogger({ app: 'test' });
177
+
178
+ logger.trace({ stack: 'trace' }, 'Trace message');
179
+
180
+ expect(console.trace).toHaveBeenCalledWith(
181
+ { app: 'test', stack: 'trace', ts: 1234567890 },
182
+ 'Trace message',
183
+ );
184
+ });
185
+
186
+ it('should log trace with only context object', () => {
187
+ const logger = new ConsoleLogger();
188
+
189
+ logger.trace({ depth: 5 });
190
+
191
+ expect(console.trace).toHaveBeenCalledWith({
192
+ depth: 5,
193
+ ts: 1234567890,
194
+ });
195
+ });
196
+ });
197
+ });
198
+
199
+ describe('Context merging', () => {
200
+ it('should merge logger context with log context', () => {
201
+ const logger = new ConsoleLogger({ app: 'myApp', env: 'production' });
202
+
203
+ logger.info({ userId: 123, action: 'login' }, 'User logged in');
204
+
205
+ expect(console.info).toHaveBeenCalledWith(
206
+ {
207
+ app: 'myApp',
208
+ env: 'production',
209
+ userId: 123,
210
+ action: 'login',
211
+ ts: 1234567890,
212
+ },
213
+ 'User logged in',
214
+ );
215
+ });
216
+
217
+ it('should override logger context with log context', () => {
218
+ const logger = new ConsoleLogger({ env: 'production', status: 'old' });
219
+
220
+ logger.info({ status: 'new', userId: 456 }, 'Status updated');
221
+
222
+ expect(console.info).toHaveBeenCalledWith(
223
+ {
224
+ env: 'production',
225
+ status: 'new', // Overridden
226
+ userId: 456,
227
+ ts: 1234567890,
228
+ },
229
+ 'Status updated',
230
+ );
231
+ });
232
+
233
+ it('should always add timestamp', () => {
234
+ const logger = new ConsoleLogger();
235
+
236
+ logger.info({ action: 'test' }, 'Test message');
237
+
238
+ expect(console.info).toHaveBeenCalledWith(
239
+ expect.objectContaining({ ts: 1234567890 }),
240
+ 'Test message',
241
+ );
242
+ });
243
+ });
244
+
245
+ describe('Additional arguments', () => {
246
+ it('should pass additional arguments to console method', () => {
247
+ const logger = new ConsoleLogger();
248
+ const obj1 = { key: 'value' };
249
+ const obj2 = { another: 'object' };
250
+
251
+ logger.info({ action: 'test' }, 'Message', obj1, obj2);
252
+
253
+ expect(console.info).toHaveBeenCalledWith(
254
+ { action: 'test', ts: 1234567890 },
255
+ 'Message',
256
+ obj1,
257
+ obj2,
258
+ );
259
+ });
260
+
261
+ it('should pass additional arguments without message', () => {
262
+ const logger = new ConsoleLogger();
263
+ const extra = 'extra data';
264
+
265
+ logger.debug({ action: 'test' }, undefined as any, extra);
266
+
267
+ expect(console.debug).toHaveBeenCalledWith(
268
+ { action: 'test', ts: 1234567890 },
269
+ extra,
270
+ );
271
+ });
272
+ });
273
+
274
+ describe('Child logger', () => {
275
+ it('should create child logger with merged context', () => {
276
+ const parentLogger = new ConsoleLogger({
277
+ app: 'myApp',
278
+ version: '1.0.0',
279
+ });
280
+ const childLogger = parentLogger.child({ module: 'auth' });
281
+
282
+ expect(childLogger.data).toEqual({
283
+ app: 'myApp',
284
+ version: '1.0.0',
285
+ module: 'auth',
286
+ });
287
+ });
288
+
289
+ it('should inherit parent context in child logs', () => {
290
+ const parentLogger = new ConsoleLogger({ app: 'myApp' });
291
+ const childLogger = parentLogger.child({ module: 'database' });
292
+
293
+ childLogger.info({ query: 'SELECT *' }, 'Query executed');
294
+
295
+ expect(console.info).toHaveBeenCalledWith(
296
+ {
297
+ app: 'myApp',
298
+ module: 'database',
299
+ query: 'SELECT *',
300
+ ts: 1234567890,
301
+ },
302
+ 'Query executed',
303
+ );
304
+ });
305
+
306
+ it('should override parent context in child logger', () => {
307
+ const parentLogger = new ConsoleLogger({ env: 'dev', status: 'parent' });
308
+ const childLogger = parentLogger.child({
309
+ status: 'child',
310
+ module: 'api',
311
+ });
312
+
313
+ expect(childLogger.data).toEqual({
314
+ env: 'dev',
315
+ status: 'child', // Overridden
316
+ module: 'api',
317
+ });
318
+ });
319
+
320
+ it('should create nested child loggers', () => {
321
+ const parentLogger = new ConsoleLogger({ app: 'myApp' });
322
+ const childLogger = parentLogger.child({ module: 'database' });
323
+ const grandchildLogger = childLogger.child({ operation: 'query' });
324
+
325
+ grandchildLogger.info({ table: 'users' }, 'Query executed');
326
+
327
+ expect(console.info).toHaveBeenCalledWith(
328
+ {
329
+ app: 'myApp',
330
+ module: 'database',
331
+ operation: 'query',
332
+ table: 'users',
333
+ ts: 1234567890,
334
+ },
335
+ 'Query executed',
336
+ );
337
+ });
338
+
339
+ it('should not affect parent logger', () => {
340
+ const parentLogger = new ConsoleLogger({ app: 'myApp' });
341
+ const childLogger = parentLogger.child({ module: 'auth' });
342
+
343
+ // Child logger has additional context
344
+ expect(childLogger.data).toEqual({ app: 'myApp', module: 'auth' });
345
+
346
+ // Parent logger is unchanged
347
+ expect(parentLogger.data).toEqual({ app: 'myApp' });
348
+ });
349
+ });
350
+
351
+ describe('Edge cases', () => {
352
+ it('should handle empty context object', () => {
353
+ const logger = new ConsoleLogger();
354
+
355
+ logger.info({}, 'Empty context');
356
+
357
+ expect(console.info).toHaveBeenCalledWith(
358
+ { ts: 1234567890 },
359
+ 'Empty context',
360
+ );
361
+ });
362
+
363
+ it('should handle complex nested objects', () => {
364
+ const logger = new ConsoleLogger();
365
+ const complexObj = {
366
+ user: {
367
+ id: 123,
368
+ profile: {
369
+ name: 'John',
370
+ tags: ['admin', 'user'],
371
+ },
372
+ },
373
+ };
374
+
375
+ logger.info(complexObj, 'Complex object');
376
+
377
+ expect(console.info).toHaveBeenCalledWith(
378
+ { ...complexObj, ts: 1234567890 },
379
+ 'Complex object',
380
+ );
381
+ });
382
+
383
+ it('should handle null and undefined values', () => {
384
+ const logger = new ConsoleLogger();
385
+
386
+ logger.info({ value: null, missing: undefined }, 'Null values');
387
+
388
+ expect(console.info).toHaveBeenCalledWith(
389
+ { value: null, missing: undefined, ts: 1234567890 },
390
+ 'Null values',
391
+ );
392
+ });
393
+
394
+ it('should handle special characters in strings', () => {
395
+ const logger = new ConsoleLogger();
396
+
397
+ logger.info({ message: 'Test\n\t"quotes"' }, 'Special chars');
398
+
399
+ expect(console.info).toHaveBeenCalledWith(
400
+ { message: 'Test\n\t"quotes"', ts: 1234567890 },
401
+ 'Special chars',
402
+ );
403
+ });
404
+ });
405
+
406
+ describe('Real-world scenarios', () => {
407
+ it('should log HTTP request', () => {
408
+ const logger = new ConsoleLogger({ service: 'api' });
409
+
410
+ logger.info(
411
+ {
412
+ method: 'POST',
413
+ path: '/users',
414
+ statusCode: 201,
415
+ duration: 45,
416
+ },
417
+ 'HTTP request completed',
418
+ );
419
+
420
+ expect(console.info).toHaveBeenCalledWith(
421
+ {
422
+ service: 'api',
423
+ method: 'POST',
424
+ path: '/users',
425
+ statusCode: 201,
426
+ duration: 45,
427
+ ts: 1234567890,
428
+ },
429
+ 'HTTP request completed',
430
+ );
431
+ });
432
+
433
+ it('should log error with stack trace', () => {
434
+ const logger = new ConsoleLogger({ service: 'worker' });
435
+ const error = new Error('Database connection failed');
436
+
437
+ logger.error(
438
+ {
439
+ error: error.message,
440
+ stack: error.stack,
441
+ operation: 'connect',
442
+ },
443
+ 'Database error',
444
+ );
445
+
446
+ expect(console.error).toHaveBeenCalledWith(
447
+ expect.objectContaining({
448
+ service: 'worker',
449
+ error: 'Database connection failed',
450
+ operation: 'connect',
451
+ ts: 1234567890,
452
+ }),
453
+ 'Database error',
454
+ );
455
+ });
456
+
457
+ it('should log business event', () => {
458
+ const logger = new ConsoleLogger({ app: 'ecommerce' });
459
+
460
+ logger.info(
461
+ {
462
+ eventType: 'order.created',
463
+ orderId: 'ORD-123',
464
+ userId: 456,
465
+ amount: 99.99,
466
+ currency: 'USD',
467
+ },
468
+ 'Order created successfully',
469
+ );
470
+
471
+ expect(console.info).toHaveBeenCalledWith(
472
+ {
473
+ app: 'ecommerce',
474
+ eventType: 'order.created',
475
+ orderId: 'ORD-123',
476
+ userId: 456,
477
+ amount: 99.99,
478
+ currency: 'USD',
479
+ ts: 1234567890,
480
+ },
481
+ 'Order created successfully',
482
+ );
483
+ });
484
+
485
+ it('should use child logger for module-specific logging', () => {
486
+ const appLogger = new ConsoleLogger({ app: 'myApp', env: 'production' });
487
+ const authLogger = appLogger.child({ module: 'auth' });
488
+ const dbLogger = appLogger.child({ module: 'database' });
489
+
490
+ authLogger.info({ userId: 123 }, 'User authenticated');
491
+ dbLogger.debug({ query: 'SELECT *', duration: 10 }, 'Query executed');
492
+
493
+ expect(console.info).toHaveBeenCalledWith(
494
+ {
495
+ app: 'myApp',
496
+ env: 'production',
497
+ module: 'auth',
498
+ userId: 123,
499
+ ts: 1234567890,
500
+ },
501
+ 'User authenticated',
502
+ );
503
+
504
+ expect(console.debug).toHaveBeenCalledWith(
505
+ {
506
+ app: 'myApp',
507
+ env: 'production',
508
+ module: 'database',
509
+ query: 'SELECT *',
510
+ duration: 10,
511
+ ts: 1234567890,
512
+ },
513
+ 'Query executed',
514
+ );
515
+ });
516
+ });
517
+
518
+ describe('DEFAULT_LOGGER', () => {
519
+ it('should export default logger instance', () => {
520
+ const { DEFAULT_LOGGER } = require('../console');
521
+
522
+ expect(DEFAULT_LOGGER).toBeDefined();
523
+ expect(DEFAULT_LOGGER.data).toBeDefined();
524
+ });
525
+ });
526
+ });
@@ -0,0 +1,156 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { LogLevel } from '../types';
3
+
4
+ // Mock pino module
5
+ vi.mock('pino', () => ({
6
+ pino: vi.fn((options) => ({
7
+ _options: options,
8
+ debug: vi.fn(),
9
+ info: vi.fn(),
10
+ warn: vi.fn(),
11
+ error: vi.fn(),
12
+ fatal: vi.fn(),
13
+ trace: vi.fn(),
14
+ })),
15
+ }));
16
+
17
+ describe('Pino Logger', () => {
18
+ let pinoMock: any;
19
+
20
+ beforeEach(async () => {
21
+ vi.clearAllMocks();
22
+ const pinoModule = await import('pino');
23
+ pinoMock = pinoModule.pino;
24
+ });
25
+
26
+ describe('createLogger', () => {
27
+ it('should create pino logger with default options', async () => {
28
+ const { createLogger } = await import('../pino');
29
+
30
+ const logger = createLogger({});
31
+
32
+ expect(pinoMock).toHaveBeenCalled();
33
+ expect(logger).toBeDefined();
34
+ });
35
+
36
+ it('should create logger with pretty formatting in development', async () => {
37
+ const { createLogger } = await import('../pino');
38
+ const originalEnv = process.env.NODE_ENV;
39
+ process.env.NODE_ENV = 'development';
40
+
41
+ createLogger({ pretty: true });
42
+
43
+ expect(pinoMock).toHaveBeenCalledWith(
44
+ expect.objectContaining({
45
+ transport: {
46
+ target: 'pino-pretty',
47
+ options: { colorize: true },
48
+ },
49
+ }),
50
+ );
51
+
52
+ process.env.NODE_ENV = originalEnv;
53
+ });
54
+
55
+ it('should not use pretty formatting when pretty is false', async () => {
56
+ const { createLogger } = await import('../pino');
57
+
58
+ createLogger({ pretty: false });
59
+
60
+ const callArgs = pinoMock.mock.calls[pinoMock.mock.calls.length - 1][0];
61
+ expect(callArgs.transport).toBeUndefined();
62
+ });
63
+
64
+ it('should configure formatters', async () => {
65
+ const { createLogger } = await import('../pino');
66
+
67
+ createLogger({});
68
+
69
+ const callArgs = pinoMock.mock.calls[pinoMock.mock.calls.length - 1][0];
70
+ expect(callArgs.formatters).toBeDefined();
71
+ expect(callArgs.formatters.bindings).toBeInstanceOf(Function);
72
+ expect(callArgs.formatters.level).toBeInstanceOf(Function);
73
+ });
74
+
75
+ it('should format bindings with node version', async () => {
76
+ const { createLogger } = await import('../pino');
77
+
78
+ createLogger({});
79
+
80
+ const callArgs = pinoMock.mock.calls[pinoMock.mock.calls.length - 1][0];
81
+ const bindings = callArgs.formatters.bindings();
82
+
83
+ expect(bindings).toEqual({
84
+ nodeVersion: process.version,
85
+ });
86
+ });
87
+
88
+ it('should format level labels to uppercase', async () => {
89
+ const { createLogger } = await import('../pino');
90
+
91
+ createLogger({});
92
+
93
+ const callArgs = pinoMock.mock.calls[pinoMock.mock.calls.length - 1][0];
94
+ const levelFormatter = callArgs.formatters.level;
95
+
96
+ expect(levelFormatter('info')).toEqual({ level: 'INFO' });
97
+ expect(levelFormatter('debug')).toEqual({ level: 'DEBUG' });
98
+ expect(levelFormatter('error')).toEqual({ level: 'ERROR' });
99
+ expect(levelFormatter('warn')).toEqual({ level: 'WARN' });
100
+ });
101
+
102
+ it('should accept log level option', async () => {
103
+ const { createLogger } = await import('../pino');
104
+
105
+ const logger = createLogger({ level: LogLevel.Debug });
106
+
107
+ expect(logger).toBeDefined();
108
+ // Note: The actual level setting depends on pino's implementation
109
+ });
110
+
111
+ it('should handle undefined options', async () => {
112
+ const { createLogger } = await import('../pino');
113
+
114
+ const logger = createLogger({});
115
+
116
+ expect(logger).toBeDefined();
117
+ expect(pinoMock).toHaveBeenCalled();
118
+ });
119
+ });
120
+
121
+ describe('Logger options', () => {
122
+ it('should support pretty option', async () => {
123
+ const { createLogger } = await import('../pino');
124
+
125
+ const logger1 = createLogger({ pretty: true });
126
+ const logger2 = createLogger({ pretty: false });
127
+
128
+ expect(logger1).toBeDefined();
129
+ expect(logger2).toBeDefined();
130
+ });
131
+
132
+ it('should support level option', async () => {
133
+ const { createLogger } = await import('../pino');
134
+
135
+ const logger1 = createLogger({ level: LogLevel.Info });
136
+ const logger2 = createLogger({ level: LogLevel.Debug });
137
+ const logger3 = createLogger({ level: LogLevel.Error });
138
+
139
+ expect(logger1).toBeDefined();
140
+ expect(logger2).toBeDefined();
141
+ expect(logger3).toBeDefined();
142
+ });
143
+
144
+ it('should support combined options', async () => {
145
+ const { createLogger } = await import('../pino');
146
+
147
+ const logger = createLogger({
148
+ pretty: true,
149
+ level: LogLevel.Debug,
150
+ });
151
+
152
+ expect(logger).toBeDefined();
153
+ expect(pinoMock).toHaveBeenCalled();
154
+ });
155
+ });
156
+ });