@unchainedshop/logger 4.3.4 → 4.4.0
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/benchmarks/benchmark.ts +1 -1
- package/lib/createLogger.d.ts +0 -15
- package/lib/createLogger.js +8 -60
- package/lib/log.d.ts +1 -2
- package/lib/log.js +1 -2
- package/lib/logger-index.d.ts +3 -4
- package/lib/logger-index.js +3 -4
- package/lib/logger.types.d.ts +8 -8
- package/lib/logger.types.js +7 -9
- package/package.json +9 -8
- package/lib/createLogger.d.ts.map +0 -1
- package/lib/createLogger.js.map +0 -1
- package/lib/log.d.ts.map +0 -1
- package/lib/log.js.map +0 -1
- package/lib/logger-index.d.ts.map +0 -1
- package/lib/logger-index.js.map +0 -1
- package/lib/logger.types.d.ts.map +0 -1
- package/lib/logger.types.js.map +0 -1
- package/src/createLogger.ts +0 -228
- package/src/log.ts +0 -7
- package/src/logger-index.ts +0 -3
- package/src/logger.types.ts +0 -7
- package/tests/createLogger.test.ts +0 -725
- package/tests/logger-index.test.ts +0 -8
- package/tsconfig.json +0 -10
|
@@ -1,725 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import { createLogger, resetLoggerInitialization } from '../src/createLogger.js';
|
|
4
|
-
|
|
5
|
-
describe('createLogger', () => {
|
|
6
|
-
const originalEnv = process.env;
|
|
7
|
-
let consoleOutput: string[] = [];
|
|
8
|
-
const originalConsole = {
|
|
9
|
-
log: console.log,
|
|
10
|
-
warn: console.warn,
|
|
11
|
-
error: console.error,
|
|
12
|
-
info: console.info,
|
|
13
|
-
debug: console.debug,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
// Reset environment variables before each test
|
|
18
|
-
process.env = { ...originalEnv };
|
|
19
|
-
// Reset logger initialization state
|
|
20
|
-
resetLoggerInitialization();
|
|
21
|
-
|
|
22
|
-
// Capture console output
|
|
23
|
-
consoleOutput = [];
|
|
24
|
-
const captureOutput = (...args: any[]) => {
|
|
25
|
-
// Join all arguments into a single string
|
|
26
|
-
const output = args
|
|
27
|
-
.map((arg) => {
|
|
28
|
-
if (typeof arg === 'string') {
|
|
29
|
-
return arg;
|
|
30
|
-
} else if (arg === undefined) {
|
|
31
|
-
return 'undefined';
|
|
32
|
-
} else if (arg === null) {
|
|
33
|
-
return 'null';
|
|
34
|
-
} else {
|
|
35
|
-
try {
|
|
36
|
-
return JSON.stringify(arg);
|
|
37
|
-
} catch {
|
|
38
|
-
return String(arg);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
.join(' ');
|
|
43
|
-
consoleOutput.push(output);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
console.log = mock.fn(captureOutput);
|
|
47
|
-
console.warn = mock.fn(captureOutput);
|
|
48
|
-
console.error = mock.fn(captureOutput);
|
|
49
|
-
console.info = mock.fn(captureOutput);
|
|
50
|
-
console.debug = mock.fn(captureOutput);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
afterEach(() => {
|
|
54
|
-
// Restore original environment
|
|
55
|
-
process.env = originalEnv;
|
|
56
|
-
// Restore console
|
|
57
|
-
console.log = originalConsole.log;
|
|
58
|
-
console.warn = originalConsole.warn;
|
|
59
|
-
console.error = originalConsole.error;
|
|
60
|
-
console.info = originalConsole.info;
|
|
61
|
-
console.debug = originalConsole.debug;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('Basic functionality', () => {
|
|
65
|
-
it('should create logger with default format (unchained)', () => {
|
|
66
|
-
const logger = createLogger('test-module');
|
|
67
|
-
assert(logger);
|
|
68
|
-
assert(typeof logger.debug === 'function');
|
|
69
|
-
assert(typeof logger.info === 'function');
|
|
70
|
-
assert(typeof logger.warn === 'function');
|
|
71
|
-
assert(typeof logger.error === 'function');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should create logger with json format', () => {
|
|
75
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
76
|
-
const logger = createLogger('test-module-json');
|
|
77
|
-
assert(logger);
|
|
78
|
-
|
|
79
|
-
// Clear previous output
|
|
80
|
-
consoleOutput = [];
|
|
81
|
-
|
|
82
|
-
// The JSON logger expects two arguments: message and metadata object
|
|
83
|
-
logger.info('test message', { metadata: 'value' });
|
|
84
|
-
|
|
85
|
-
// Check if we got output
|
|
86
|
-
assert(consoleOutput.length > 0, 'No console output captured');
|
|
87
|
-
|
|
88
|
-
// The output should be a single JSON string
|
|
89
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
90
|
-
|
|
91
|
-
// Verify JSON format output
|
|
92
|
-
const parsed = JSON.parse(lastOutput);
|
|
93
|
-
assert(parsed.level === 'INFO');
|
|
94
|
-
assert(parsed.name === 'test-module-json');
|
|
95
|
-
assert(parsed.message === 'test message');
|
|
96
|
-
assert(parsed.metadata === 'value');
|
|
97
|
-
assert(parsed.timestamp); // Should have a timestamp
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should throw error with invalid log format', () => {
|
|
101
|
-
process.env.UNCHAINED_LOG_FORMAT = 'invalid';
|
|
102
|
-
assert.throws(() => {
|
|
103
|
-
createLogger('test-module-invalid');
|
|
104
|
-
}, /UNCHAINED_LOG_FORMAT is invalid/);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe('DEBUG environment variable patterns', () => {
|
|
109
|
-
it('should respect simple DEBUG pattern', () => {
|
|
110
|
-
process.env.DEBUG = 'test-debug-module';
|
|
111
|
-
process.env.LOG_LEVEL = 'info';
|
|
112
|
-
|
|
113
|
-
const logger = createLogger('test-debug-module');
|
|
114
|
-
logger.debug('debug message');
|
|
115
|
-
|
|
116
|
-
// Debug message should appear
|
|
117
|
-
assert(consoleOutput.some((output) => output.includes('debug message')));
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should handle wildcard patterns', () => {
|
|
121
|
-
process.env.DEBUG = 'test-*';
|
|
122
|
-
process.env.LOG_LEVEL = 'info';
|
|
123
|
-
|
|
124
|
-
const logger1 = createLogger('test-module-1');
|
|
125
|
-
const logger2 = createLogger('test-module-2');
|
|
126
|
-
const logger3 = createLogger('other-module');
|
|
127
|
-
|
|
128
|
-
logger1.debug('debug1');
|
|
129
|
-
logger2.debug('debug2');
|
|
130
|
-
logger3.debug('debug3');
|
|
131
|
-
|
|
132
|
-
// Only test-* modules should output debug
|
|
133
|
-
assert(consoleOutput.some((output) => output.includes('debug1')));
|
|
134
|
-
assert(consoleOutput.some((output) => output.includes('debug2')));
|
|
135
|
-
assert(!consoleOutput.some((output) => output.includes('debug3')));
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should handle exclusion patterns', () => {
|
|
139
|
-
process.env.DEBUG = 'test-*,-test-exclude';
|
|
140
|
-
process.env.LOG_LEVEL = 'info';
|
|
141
|
-
|
|
142
|
-
const includedLogger = createLogger('test-included');
|
|
143
|
-
const excludedLogger = createLogger('test-exclude');
|
|
144
|
-
|
|
145
|
-
includedLogger.debug('included');
|
|
146
|
-
excludedLogger.debug('excluded');
|
|
147
|
-
|
|
148
|
-
assert(consoleOutput.some((output) => output.includes('included')));
|
|
149
|
-
assert(!consoleOutput.some((output) => output.includes('excluded')));
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should handle multiple patterns', () => {
|
|
153
|
-
process.env.DEBUG = 'auth-*,payment-*,-payment-debug';
|
|
154
|
-
process.env.LOG_LEVEL = 'info';
|
|
155
|
-
|
|
156
|
-
const authLogger = createLogger('auth-service');
|
|
157
|
-
const paymentLogger = createLogger('payment-service');
|
|
158
|
-
const paymentDebugLogger = createLogger('payment-debug');
|
|
159
|
-
const otherLogger = createLogger('other-service');
|
|
160
|
-
|
|
161
|
-
authLogger.debug('auth debug');
|
|
162
|
-
paymentLogger.debug('payment debug');
|
|
163
|
-
paymentDebugLogger.debug('payment-debug debug');
|
|
164
|
-
otherLogger.debug('other debug');
|
|
165
|
-
|
|
166
|
-
assert(consoleOutput.some((output) => output.includes('auth debug')));
|
|
167
|
-
assert(consoleOutput.some((output) => output.includes('payment debug')));
|
|
168
|
-
assert(!consoleOutput.some((output) => output.includes('payment-debug debug')));
|
|
169
|
-
assert(!consoleOutput.some((output) => output.includes('other debug')));
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('should handle colon in module names', () => {
|
|
173
|
-
process.env.DEBUG = 'module:*';
|
|
174
|
-
|
|
175
|
-
const logger1 = createLogger('module:sub1');
|
|
176
|
-
const logger2 = createLogger('module:sub2');
|
|
177
|
-
const logger3 = createLogger('other-module');
|
|
178
|
-
|
|
179
|
-
logger1.debug('debug1');
|
|
180
|
-
logger2.debug('debug2');
|
|
181
|
-
logger3.debug('debug3');
|
|
182
|
-
|
|
183
|
-
assert(consoleOutput.some((output) => output.includes('debug1')));
|
|
184
|
-
assert(consoleOutput.some((output) => output.includes('debug2')));
|
|
185
|
-
assert(!consoleOutput.some((output) => output.includes('debug3')));
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
describe('Circular dependencies in JSON format', () => {
|
|
190
|
-
it('should handle circular references in objects', () => {
|
|
191
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
192
|
-
const logger = createLogger('circular-test');
|
|
193
|
-
|
|
194
|
-
const obj1: any = { name: 'obj1' };
|
|
195
|
-
const obj2: any = { name: 'obj2', ref: obj1 };
|
|
196
|
-
obj1.ref = obj2; // Create circular reference
|
|
197
|
-
|
|
198
|
-
consoleOutput = [];
|
|
199
|
-
logger.info('circular object', { data: obj1 });
|
|
200
|
-
|
|
201
|
-
// Should not throw and should produce valid JSON
|
|
202
|
-
assert(consoleOutput.length > 0, 'No console output captured');
|
|
203
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
204
|
-
|
|
205
|
-
assert.doesNotThrow(() => JSON.parse(lastOutput));
|
|
206
|
-
|
|
207
|
-
const parsed = JSON.parse(lastOutput);
|
|
208
|
-
assert(parsed.message === 'circular object');
|
|
209
|
-
assert(parsed.data.name === 'obj1');
|
|
210
|
-
// Circular reference should be replaced with "[Circular]"
|
|
211
|
-
assert(parsed.data.ref.ref === '[Circular]');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should handle self-referencing objects', () => {
|
|
215
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
216
|
-
const logger = createLogger('self-ref-test');
|
|
217
|
-
|
|
218
|
-
const obj: any = { name: 'self' };
|
|
219
|
-
obj.self = obj;
|
|
220
|
-
|
|
221
|
-
consoleOutput = [];
|
|
222
|
-
logger.info('self reference', { data: obj });
|
|
223
|
-
|
|
224
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
225
|
-
assert.doesNotThrow(() => JSON.parse(lastOutput));
|
|
226
|
-
|
|
227
|
-
const parsed = JSON.parse(lastOutput);
|
|
228
|
-
assert(parsed.data.name === 'self');
|
|
229
|
-
assert(parsed.data.self === '[Circular]');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should handle deeply nested circular references', () => {
|
|
233
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
234
|
-
const logger = createLogger('deep-circular-test');
|
|
235
|
-
|
|
236
|
-
const obj: any = {
|
|
237
|
-
level1: {
|
|
238
|
-
level2: {
|
|
239
|
-
level3: {},
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
obj.level1.level2.level3.circular = obj;
|
|
244
|
-
|
|
245
|
-
consoleOutput = [];
|
|
246
|
-
logger.info('deep circular', { data: obj });
|
|
247
|
-
|
|
248
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
249
|
-
assert.doesNotThrow(() => JSON.parse(lastOutput));
|
|
250
|
-
|
|
251
|
-
const parsed = JSON.parse(lastOutput);
|
|
252
|
-
assert(parsed.data.level1.level2.level3.circular === '[Circular]');
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
describe('Error object logging', () => {
|
|
257
|
-
it('should log Error objects in unchained format', () => {
|
|
258
|
-
const logger = createLogger('error-test');
|
|
259
|
-
const error = new Error('Test error message');
|
|
260
|
-
|
|
261
|
-
logger.error('Error occurred', error);
|
|
262
|
-
|
|
263
|
-
assert(consoleOutput.some((output) => output.includes('Error occurred')));
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it('should log Error objects in JSON format', () => {
|
|
267
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
268
|
-
const logger = createLogger('error-json-test');
|
|
269
|
-
|
|
270
|
-
const error = new Error('Test error message');
|
|
271
|
-
error.stack = 'Error: Test error message\n at Test.fn';
|
|
272
|
-
|
|
273
|
-
consoleOutput = [];
|
|
274
|
-
logger.error('Error occurred', { error });
|
|
275
|
-
|
|
276
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
277
|
-
const parsed = JSON.parse(lastOutput);
|
|
278
|
-
assert(parsed.message === 'Error occurred');
|
|
279
|
-
assert(parsed.level === 'ERROR');
|
|
280
|
-
// Error objects are serialized as empty objects by default in JSON
|
|
281
|
-
assert(typeof parsed.error === 'object');
|
|
282
|
-
assert(Object.keys(parsed.error).length === 0);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('should handle custom error properties', () => {
|
|
286
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
287
|
-
const logger = createLogger('custom-error-test');
|
|
288
|
-
|
|
289
|
-
class CustomError extends Error {
|
|
290
|
-
code: string;
|
|
291
|
-
statusCode: number;
|
|
292
|
-
|
|
293
|
-
constructor(message: string, code: string, statusCode: number) {
|
|
294
|
-
super(message);
|
|
295
|
-
this.code = code;
|
|
296
|
-
this.statusCode = statusCode;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const error = new CustomError('Custom error', 'ERR_CUSTOM', 400);
|
|
301
|
-
|
|
302
|
-
consoleOutput = [];
|
|
303
|
-
logger.error('Custom error occurred', { error, additionalInfo: 'extra' });
|
|
304
|
-
|
|
305
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
306
|
-
const parsed = JSON.parse(lastOutput);
|
|
307
|
-
assert(parsed.additionalInfo === 'extra');
|
|
308
|
-
assert(parsed.level === 'ERROR');
|
|
309
|
-
// Error objects including custom errors are serialized as empty objects
|
|
310
|
-
assert(typeof parsed.error === 'object');
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('should handle Error with circular references', () => {
|
|
314
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
315
|
-
const logger = createLogger('circular-error-test');
|
|
316
|
-
|
|
317
|
-
const error: any = new Error('Circular error');
|
|
318
|
-
error.circular = error;
|
|
319
|
-
|
|
320
|
-
consoleOutput = [];
|
|
321
|
-
logger.error('Circular error', { error });
|
|
322
|
-
|
|
323
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
324
|
-
assert.doesNotThrow(() => JSON.parse(lastOutput));
|
|
325
|
-
|
|
326
|
-
const parsed = JSON.parse(lastOutput);
|
|
327
|
-
assert(parsed.level === 'ERROR');
|
|
328
|
-
// Error is serialized as empty object, circular property is ignored
|
|
329
|
-
assert(typeof parsed.error === 'object');
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
describe('Edge cases and special values', () => {
|
|
334
|
-
it('should handle null values', () => {
|
|
335
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
336
|
-
const logger = createLogger('null-test');
|
|
337
|
-
|
|
338
|
-
consoleOutput = [];
|
|
339
|
-
logger.info('null value', { value: null });
|
|
340
|
-
|
|
341
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
342
|
-
const parsed = JSON.parse(lastOutput);
|
|
343
|
-
assert(parsed.value === null);
|
|
344
|
-
assert(parsed.message === 'null value');
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('should handle undefined values', () => {
|
|
348
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
349
|
-
const logger = createLogger('undefined-test');
|
|
350
|
-
|
|
351
|
-
consoleOutput = [];
|
|
352
|
-
logger.info('undefined value', { value: undefined });
|
|
353
|
-
|
|
354
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
355
|
-
const parsed = JSON.parse(lastOutput);
|
|
356
|
-
// undefined should be omitted in JSON
|
|
357
|
-
assert(!('value' in parsed));
|
|
358
|
-
assert(parsed.message === 'undefined value');
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it('should handle NaN and Infinity', () => {
|
|
362
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
363
|
-
const logger = createLogger('nan-infinity-test');
|
|
364
|
-
|
|
365
|
-
consoleOutput = [];
|
|
366
|
-
logger.info('special numbers', {
|
|
367
|
-
nan: NaN,
|
|
368
|
-
infinity: Infinity,
|
|
369
|
-
negInfinity: -Infinity,
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
373
|
-
const parsed = JSON.parse(lastOutput);
|
|
374
|
-
// NaN and Infinity are serialized as null by safe-stable-stringify
|
|
375
|
-
assert(parsed.nan === null);
|
|
376
|
-
assert(parsed.infinity === null);
|
|
377
|
-
assert(parsed.negInfinity === null);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it('should handle BigInt values', () => {
|
|
381
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
382
|
-
const logger = createLogger('bigint-test');
|
|
383
|
-
|
|
384
|
-
consoleOutput = [];
|
|
385
|
-
logger.info('bigint value', { value: BigInt('9007199254740993') });
|
|
386
|
-
|
|
387
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
388
|
-
const parsed = JSON.parse(lastOutput);
|
|
389
|
-
// BigInt is serialized as string by safe-stable-stringify
|
|
390
|
-
assert(typeof parsed.value === 'string');
|
|
391
|
-
assert(parsed.value === '9007199254740993');
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('should handle Symbol values', () => {
|
|
395
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
396
|
-
const logger = createLogger('symbol-test');
|
|
397
|
-
|
|
398
|
-
const sym = Symbol('test');
|
|
399
|
-
|
|
400
|
-
consoleOutput = [];
|
|
401
|
-
logger.info('symbol value', { value: sym });
|
|
402
|
-
|
|
403
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
404
|
-
const parsed = JSON.parse(lastOutput);
|
|
405
|
-
// Symbols should be omitted in JSON
|
|
406
|
-
assert(!('value' in parsed));
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it('should handle functions', () => {
|
|
410
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
411
|
-
const logger = createLogger('function-test');
|
|
412
|
-
|
|
413
|
-
const fn = () => 'test';
|
|
414
|
-
|
|
415
|
-
consoleOutput = [];
|
|
416
|
-
logger.info('function value', { value: fn });
|
|
417
|
-
|
|
418
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
419
|
-
const parsed = JSON.parse(lastOutput);
|
|
420
|
-
// Functions should be omitted in JSON
|
|
421
|
-
assert(!('value' in parsed));
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it('should handle Date objects', () => {
|
|
425
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
426
|
-
const logger = createLogger('date-test');
|
|
427
|
-
|
|
428
|
-
const date = new Date('2023-01-01T00:00:00.000Z');
|
|
429
|
-
|
|
430
|
-
consoleOutput = [];
|
|
431
|
-
logger.info('date value', { value: date });
|
|
432
|
-
|
|
433
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
434
|
-
const parsed = JSON.parse(lastOutput);
|
|
435
|
-
// Dates are serialized as ISO strings
|
|
436
|
-
assert(parsed.value === '2023-01-01T00:00:00.000Z');
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('should handle RegExp objects', () => {
|
|
440
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
441
|
-
const logger = createLogger('regexp-test');
|
|
442
|
-
|
|
443
|
-
const regex = /test/gi;
|
|
444
|
-
|
|
445
|
-
consoleOutput = [];
|
|
446
|
-
logger.info('regex value', { value: regex });
|
|
447
|
-
|
|
448
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
449
|
-
const parsed = JSON.parse(lastOutput);
|
|
450
|
-
// RegExp objects are serialized as empty objects in JSON
|
|
451
|
-
assert(typeof parsed.value === 'object' && Object.keys(parsed.value).length === 0);
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
it('should handle empty strings', () => {
|
|
455
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
456
|
-
const logger = createLogger('empty-string-test');
|
|
457
|
-
|
|
458
|
-
consoleOutput = [];
|
|
459
|
-
logger.info('', { emptyKey: '' });
|
|
460
|
-
|
|
461
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
462
|
-
const parsed = JSON.parse(lastOutput);
|
|
463
|
-
assert(parsed.message === '');
|
|
464
|
-
assert(parsed.emptyKey === '');
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
it('should handle arrays with mixed types', () => {
|
|
468
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
469
|
-
const logger = createLogger('mixed-array-test');
|
|
470
|
-
|
|
471
|
-
consoleOutput = [];
|
|
472
|
-
logger.info('mixed array', {
|
|
473
|
-
array: [1, 'string', null, undefined, { nested: true }, [1, 2, 3]],
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
477
|
-
const parsed = JSON.parse(lastOutput);
|
|
478
|
-
assert(Array.isArray(parsed.array));
|
|
479
|
-
assert(parsed.array[0] === 1);
|
|
480
|
-
assert(parsed.array[1] === 'string');
|
|
481
|
-
assert(parsed.array[2] === null);
|
|
482
|
-
assert(parsed.array[3] === null); // undefined becomes null in JSON
|
|
483
|
-
assert(parsed.array[4].nested === true);
|
|
484
|
-
assert(Array.isArray(parsed.array[5]));
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
describe('Different log levels', () => {
|
|
489
|
-
it('should respect LOG_LEVEL environment variable', () => {
|
|
490
|
-
process.env.LOG_LEVEL = 'error';
|
|
491
|
-
const logger = createLogger('log-level-test');
|
|
492
|
-
|
|
493
|
-
logger.debug('debug message');
|
|
494
|
-
logger.info('info message');
|
|
495
|
-
logger.warn('warn message');
|
|
496
|
-
logger.error('error message');
|
|
497
|
-
|
|
498
|
-
// Only error messages should appear
|
|
499
|
-
assert(!consoleOutput.some((output) => output.includes('debug message')));
|
|
500
|
-
assert(!consoleOutput.some((output) => output.includes('info message')));
|
|
501
|
-
assert(!consoleOutput.some((output) => output.includes('warn message')));
|
|
502
|
-
assert(consoleOutput.some((output) => output.includes('error message')));
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
it('should handle all log levels', () => {
|
|
506
|
-
process.env.LOG_LEVEL = 'verbose';
|
|
507
|
-
const logger = createLogger('all-levels-test');
|
|
508
|
-
|
|
509
|
-
logger.trace('trace message');
|
|
510
|
-
logger.debug('debug message');
|
|
511
|
-
logger.info('info message');
|
|
512
|
-
logger.warn('warn message');
|
|
513
|
-
logger.error('error message');
|
|
514
|
-
|
|
515
|
-
// All messages should appear
|
|
516
|
-
assert(consoleOutput.some((output) => output.includes('trace message')));
|
|
517
|
-
assert(consoleOutput.some((output) => output.includes('debug message')));
|
|
518
|
-
assert(consoleOutput.some((output) => output.includes('info message')));
|
|
519
|
-
assert(consoleOutput.some((output) => output.includes('warn message')));
|
|
520
|
-
assert(consoleOutput.some((output) => output.includes('error message')));
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
it('should handle case-insensitive log levels', () => {
|
|
524
|
-
process.env.LOG_LEVEL = 'WaRn';
|
|
525
|
-
const logger = createLogger('case-test');
|
|
526
|
-
|
|
527
|
-
consoleOutput = [];
|
|
528
|
-
logger.info('info message');
|
|
529
|
-
logger.warn('warn message');
|
|
530
|
-
logger.error('error message');
|
|
531
|
-
|
|
532
|
-
assert(!consoleOutput.some((output) => output.includes('info message')));
|
|
533
|
-
assert(consoleOutput.some((output) => output.includes('warn message')));
|
|
534
|
-
assert(consoleOutput.some((output) => output.includes('error message')));
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
describe('Multiple logger instances', () => {
|
|
539
|
-
it('should maintain separate loggers for different modules', () => {
|
|
540
|
-
process.env.DEBUG = 'module1';
|
|
541
|
-
|
|
542
|
-
const logger1 = createLogger('module1');
|
|
543
|
-
const logger2 = createLogger('module2');
|
|
544
|
-
|
|
545
|
-
logger1.debug('module1 debug');
|
|
546
|
-
logger2.debug('module2 debug');
|
|
547
|
-
|
|
548
|
-
assert(consoleOutput.some((output) => output.includes('module1 debug')));
|
|
549
|
-
assert(!consoleOutput.some((output) => output.includes('module2 debug')));
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
it('should share format configuration across instances', () => {
|
|
553
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
554
|
-
|
|
555
|
-
consoleOutput = [];
|
|
556
|
-
const logger1 = createLogger('json-module1');
|
|
557
|
-
const logger2 = createLogger('json-module2');
|
|
558
|
-
|
|
559
|
-
logger1.info('message1');
|
|
560
|
-
logger2.info('message2');
|
|
561
|
-
|
|
562
|
-
// Both should output JSON
|
|
563
|
-
assert(consoleOutput.length === 2);
|
|
564
|
-
|
|
565
|
-
const parsed1 = JSON.parse(consoleOutput[0]);
|
|
566
|
-
const parsed2 = JSON.parse(consoleOutput[1]);
|
|
567
|
-
|
|
568
|
-
assert(parsed1.name === 'json-module1');
|
|
569
|
-
assert(parsed1.message === 'message1');
|
|
570
|
-
assert(parsed1.level === 'INFO');
|
|
571
|
-
|
|
572
|
-
assert(parsed2.name === 'json-module2');
|
|
573
|
-
assert(parsed2.message === 'message2');
|
|
574
|
-
assert(parsed2.level === 'INFO');
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
describe('Security', () => {
|
|
579
|
-
it('should guard against prototype pollution via __proto__', () => {
|
|
580
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
581
|
-
const logger = createLogger('prototype-pollution-test');
|
|
582
|
-
|
|
583
|
-
const maliciousPayload = JSON.parse('{"__proto__": {"polluted": "value"}}');
|
|
584
|
-
|
|
585
|
-
consoleOutput = [];
|
|
586
|
-
logger.info('test', maliciousPayload);
|
|
587
|
-
|
|
588
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
589
|
-
const parsed = JSON.parse(lastOutput);
|
|
590
|
-
|
|
591
|
-
// Should not pollute Object prototype
|
|
592
|
-
assert.strictEqual('polluted' in Object.prototype, false);
|
|
593
|
-
assert.strictEqual('polluted' in {}, false);
|
|
594
|
-
// The dangerous __proto__ key should be filtered out
|
|
595
|
-
// Check that we only have expected keys
|
|
596
|
-
const keys = Object.keys(parsed);
|
|
597
|
-
assert.strictEqual(keys.includes('__proto__'), false);
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
it('should guard against prototype pollution via constructor', () => {
|
|
601
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
602
|
-
const logger = createLogger('constructor-pollution-test');
|
|
603
|
-
|
|
604
|
-
const maliciousPayload = { constructor: { polluted: 'value' } };
|
|
605
|
-
|
|
606
|
-
consoleOutput = [];
|
|
607
|
-
logger.info('test', maliciousPayload);
|
|
608
|
-
|
|
609
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
610
|
-
const parsed = JSON.parse(lastOutput);
|
|
611
|
-
|
|
612
|
-
// The dangerous constructor key should be filtered out
|
|
613
|
-
const keys = Object.keys(parsed);
|
|
614
|
-
assert.strictEqual(keys.includes('constructor'), false);
|
|
615
|
-
// Verify the constructor wasn't overridden with the malicious payload
|
|
616
|
-
assert.notStrictEqual(parsed.constructor, maliciousPayload.constructor);
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it('should guard against prototype pollution via prototype', () => {
|
|
620
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
621
|
-
const logger = createLogger('prototype-key-test');
|
|
622
|
-
|
|
623
|
-
const maliciousPayload = { prototype: { polluted: 'value' } };
|
|
624
|
-
|
|
625
|
-
consoleOutput = [];
|
|
626
|
-
logger.info('test', maliciousPayload);
|
|
627
|
-
|
|
628
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
629
|
-
const parsed = JSON.parse(lastOutput);
|
|
630
|
-
|
|
631
|
-
// The dangerous prototype key should be filtered out
|
|
632
|
-
const keys = Object.keys(parsed);
|
|
633
|
-
assert.strictEqual(keys.includes('prototype'), false);
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
it('should allow safe keys while blocking dangerous ones', () => {
|
|
637
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
638
|
-
const logger = createLogger('mixed-keys-test');
|
|
639
|
-
|
|
640
|
-
const mixedPayload = {
|
|
641
|
-
safeKey: 'safe value',
|
|
642
|
-
__proto__: { dangerous: 'value' },
|
|
643
|
-
anotherSafe: 123,
|
|
644
|
-
constructor: { dangerous: 'value' },
|
|
645
|
-
validKey: true,
|
|
646
|
-
prototype: { dangerous: 'value' },
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
consoleOutput = [];
|
|
650
|
-
logger.info('test', mixedPayload);
|
|
651
|
-
|
|
652
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
653
|
-
const parsed = JSON.parse(lastOutput);
|
|
654
|
-
|
|
655
|
-
// Safe keys should be present
|
|
656
|
-
assert.strictEqual(parsed.safeKey, 'safe value');
|
|
657
|
-
assert.strictEqual(parsed.anotherSafe, 123);
|
|
658
|
-
assert.strictEqual(parsed.validKey, true);
|
|
659
|
-
|
|
660
|
-
// Dangerous keys should be filtered out
|
|
661
|
-
const keys = Object.keys(parsed);
|
|
662
|
-
assert.strictEqual(keys.includes('__proto__'), false);
|
|
663
|
-
assert.strictEqual(keys.includes('constructor'), false);
|
|
664
|
-
assert.strictEqual(keys.includes('prototype'), false);
|
|
665
|
-
});
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
describe('Format edge cases', () => {
|
|
669
|
-
it('should handle very long messages', () => {
|
|
670
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
671
|
-
const logger = createLogger('long-message-test');
|
|
672
|
-
|
|
673
|
-
consoleOutput = [];
|
|
674
|
-
const longMessage = 'x'.repeat(10000);
|
|
675
|
-
logger.info(longMessage);
|
|
676
|
-
|
|
677
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
678
|
-
const parsed = JSON.parse(lastOutput);
|
|
679
|
-
assert(parsed.message.length === 10000);
|
|
680
|
-
assert(parsed.message === longMessage);
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
it('should handle special characters in messages', () => {
|
|
684
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
685
|
-
const logger = createLogger('special-chars-test');
|
|
686
|
-
|
|
687
|
-
consoleOutput = [];
|
|
688
|
-
logger.info('Special chars: \n\r\t"\'\\', {
|
|
689
|
-
data: 'Line 1\nLine 2\tTabbed',
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
693
|
-
const parsed = JSON.parse(lastOutput);
|
|
694
|
-
assert(parsed.message.includes('\n'));
|
|
695
|
-
assert(parsed.message.includes('\r'));
|
|
696
|
-
assert(parsed.message.includes('\t'));
|
|
697
|
-
assert(parsed.message.includes('"'));
|
|
698
|
-
assert(parsed.message.includes("'"));
|
|
699
|
-
assert(parsed.message.includes('\\'));
|
|
700
|
-
assert(parsed.data.includes('\n'));
|
|
701
|
-
assert(parsed.data.includes('\t'));
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
it('should handle unicode characters', () => {
|
|
705
|
-
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
706
|
-
const logger = createLogger('unicode-test');
|
|
707
|
-
|
|
708
|
-
consoleOutput = [];
|
|
709
|
-
logger.info('Unicode: 🔥 emoji, 中文, العربية', {
|
|
710
|
-
emoji: '🚀',
|
|
711
|
-
chinese: '你好',
|
|
712
|
-
arabic: 'مرحبا',
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
const lastOutput = consoleOutput[consoleOutput.length - 1];
|
|
716
|
-
const parsed = JSON.parse(lastOutput);
|
|
717
|
-
assert(parsed.message.includes('🔥'));
|
|
718
|
-
assert(parsed.message.includes('中文'));
|
|
719
|
-
assert(parsed.message.includes('العربية'));
|
|
720
|
-
assert(parsed.emoji === '🚀');
|
|
721
|
-
assert(parsed.chinese === '你好');
|
|
722
|
-
assert(parsed.arabic === 'مرحبا');
|
|
723
|
-
});
|
|
724
|
-
});
|
|
725
|
-
});
|