@unchainedshop/logger 4.0.0-rc.9 → 4.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.
- package/README.md +94 -2
- package/benchmarks/README.md +32 -0
- package/benchmarks/benchmark.ts +273 -0
- package/lib/createLogger.d.ts +23 -2
- package/lib/createLogger.d.ts.map +1 -1
- package/lib/createLogger.js +175 -50
- package/lib/createLogger.js.map +1 -1
- package/lib/log.d.ts +2 -2
- package/lib/log.d.ts.map +1 -1
- package/package.json +3 -5
- package/src/createLogger.ts +199 -56
- package/tests/createLogger.test.ts +725 -0
- package/tsconfig.json +3 -2
package/README.md
CHANGED
|
@@ -1,5 +1,97 @@
|
|
|
1
1
|
# Logger (Unchained Engine)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A high-performance, feature-rich logging package for Unchained Engine with support for multiple formats, log levels, and debug patterns.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **High Performance**: Optimized for speed with caching and no-op functions for disabled log levels
|
|
8
|
+
- 🎨 **Multiple Formats**: Support for both human-readable (unchained) and JSON formats
|
|
9
|
+
- 🔍 **Debug Patterns**: Flexible DEBUG environment variable with wildcards and exclusions
|
|
10
|
+
- 📊 **Log Levels**: Five log levels (trace, debug, info, warn, error) with environment-based filtering
|
|
11
|
+
- 💪 **TypeScript**: Full TypeScript support with type definitions
|
|
12
|
+
- 🔧 **Zero Dependencies**: Core functionality with minimal external dependencies
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @unchainedshop/logger
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Usage
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
26
|
+
|
|
27
|
+
const logger = createLogger('my-module');
|
|
28
|
+
|
|
29
|
+
logger.info('Application started');
|
|
30
|
+
logger.debug('Debug information', { userId: 123 });
|
|
31
|
+
logger.error('Something went wrong', new Error('Details'));
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Environment Variables
|
|
35
|
+
|
|
36
|
+
#### `UNCHAINED_LOG_FORMAT`
|
|
37
|
+
Controls the output format:
|
|
38
|
+
- `unchained` (default): Human-readable format with colors
|
|
39
|
+
- `json`: Machine-readable JSON format
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
UNCHAINED_LOG_FORMAT=json node app.js
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### `LOG_LEVEL`
|
|
46
|
+
Sets the minimum log level:
|
|
47
|
+
- `verbose` or `trace`: All logs
|
|
48
|
+
- `debug`: Debug and above
|
|
49
|
+
- `info` (default): Info and above
|
|
50
|
+
- `warn`: Warnings and errors only
|
|
51
|
+
- `error`: Errors only
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
LOG_LEVEL=debug node app.js
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### `DEBUG`
|
|
58
|
+
Enables debug logging for specific modules using pattern matching:
|
|
59
|
+
- Wildcards: `DEBUG=app:*`
|
|
60
|
+
- Exclusions: `DEBUG=*,-app:excluded`
|
|
61
|
+
- Multiple patterns: `DEBUG=app:*,core:*`
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
DEBUG=my-module,other:* node app.js
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
### `createLogger(moduleName: string): Logger`
|
|
70
|
+
|
|
71
|
+
Creates a logger instance for the specified module.
|
|
72
|
+
|
|
73
|
+
**Parameters:**
|
|
74
|
+
- `moduleName` - The name of the module (used for filtering and display)
|
|
75
|
+
|
|
76
|
+
**Returns:** Logger instance with methods: `trace`, `debug`, `info`, `warn`, `error`
|
|
77
|
+
|
|
78
|
+
### Default Logger
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { log, defaultLogger } from '@unchainedshop/logger';
|
|
82
|
+
|
|
83
|
+
// Quick logging with default logger
|
|
84
|
+
log('Quick info message');
|
|
85
|
+
|
|
86
|
+
// Or use the default logger directly
|
|
87
|
+
defaultLogger.info('Info message');
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Performance
|
|
91
|
+
|
|
92
|
+
See [benchmarks/README.md](./benchmarks/README.md) for detailed performance metrics.
|
|
93
|
+
|
|
94
|
+
Key performance features:
|
|
95
|
+
- **Zero-cost disabled logging**: Log levels below the minimum use no-op functions
|
|
96
|
+
- **Regex caching**: Pattern matching results are cached for speed
|
|
97
|
+
- **Optimized hot paths**: Critical logging paths are optimized for maximum throughput
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Logger Performance Benchmarks
|
|
2
|
+
|
|
3
|
+
Run with: `npm run benchmark`
|
|
4
|
+
|
|
5
|
+
## Latest Results (October 7, 2025)
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Test Name | Ops/sec | Avg Time | Total Time
|
|
9
|
+
---------------------------------|--------------|--------------|-------------
|
|
10
|
+
Logger creation (default format) | 935.97K | 0.001 ms | 1.07 ms
|
|
11
|
+
Logger creation (JSON format) | 1.27M | 0.79 µs | 0.79 ms
|
|
12
|
+
Log info (default format) | 8.15M | 0.12 µs | 12.28 ms
|
|
13
|
+
Log info (JSON format) | 1.25M | 0.80 µs | 79.87 ms
|
|
14
|
+
Log complex object (JSON) | 465.65K | 0.002 ms | 21.48 ms
|
|
15
|
+
Debug log (enabled) | 8.34M | 0.12 µs | 11.99 ms
|
|
16
|
+
Debug log (disabled) | 299.36M | 0.00 µs | 0.33 ms
|
|
17
|
+
Pattern matching (createLogger) | 1.21M | 0.82 µs | 4.12 ms
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Key Insights
|
|
21
|
+
|
|
22
|
+
- JSON format logging is **550.6% slower** than default format
|
|
23
|
+
- Debug logging when disabled is **35.9x faster** (skips log processing entirely)
|
|
24
|
+
- Fastest: Debug log (disabled) at 299.36M ops/s
|
|
25
|
+
- Slowest: Complex object logging at 465.65K ops/s
|
|
26
|
+
|
|
27
|
+
## Optimizations
|
|
28
|
+
|
|
29
|
+
The current implementation includes targeted optimizations:
|
|
30
|
+
- **Regex pattern caching**: Compiled RegExp objects are cached to avoid recreation
|
|
31
|
+
- **Pattern result caching**: DEBUG pattern matching results are cached per module
|
|
32
|
+
- **No-op functions**: Disabled log levels return empty functions for zero-cost logging
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
2
|
+
import { createLogger, resetLoggerInitialization } from '../src/createLogger.js';
|
|
3
|
+
|
|
4
|
+
interface BenchmarkResult {
|
|
5
|
+
name: string;
|
|
6
|
+
totalTime: number;
|
|
7
|
+
operations: number;
|
|
8
|
+
opsPerSecond: number;
|
|
9
|
+
avgTimeMs: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class LoggerBenchmark {
|
|
13
|
+
private originalConsole = {
|
|
14
|
+
log: console.log,
|
|
15
|
+
info: console.info,
|
|
16
|
+
warn: console.warn,
|
|
17
|
+
error: console.error,
|
|
18
|
+
debug: console.debug,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
private suppressConsole() {
|
|
22
|
+
const noop = () => {
|
|
23
|
+
/* intentionally empty */
|
|
24
|
+
};
|
|
25
|
+
console.log = noop;
|
|
26
|
+
console.info = noop;
|
|
27
|
+
console.warn = noop;
|
|
28
|
+
console.error = noop;
|
|
29
|
+
console.debug = noop;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private restoreConsole() {
|
|
33
|
+
Object.assign(console, this.originalConsole);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private benchmark(name: string, iterations: number, fn: () => void): BenchmarkResult {
|
|
37
|
+
// Warm-up phase
|
|
38
|
+
for (let i = 0; i < Math.min(100, iterations / 10); i++) {
|
|
39
|
+
fn();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Actual benchmark
|
|
43
|
+
const start = performance.now();
|
|
44
|
+
for (let i = 0; i < iterations; i++) {
|
|
45
|
+
fn();
|
|
46
|
+
}
|
|
47
|
+
const totalTime = performance.now() - start;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
totalTime,
|
|
52
|
+
operations: iterations,
|
|
53
|
+
opsPerSecond: (iterations / totalTime) * 1000,
|
|
54
|
+
avgTimeMs: totalTime / iterations,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async run() {
|
|
59
|
+
this.restoreConsole();
|
|
60
|
+
console.log('🚀 Logger Performance Benchmark\n');
|
|
61
|
+
console.log('Running benchmarks...\n');
|
|
62
|
+
|
|
63
|
+
const results: BenchmarkResult[] = [];
|
|
64
|
+
|
|
65
|
+
// Test 1: Logger creation performance
|
|
66
|
+
this.suppressConsole();
|
|
67
|
+
resetLoggerInitialization();
|
|
68
|
+
delete process.env.UNCHAINED_LOG_FORMAT;
|
|
69
|
+
delete process.env.DEBUG;
|
|
70
|
+
delete process.env.LOG_LEVEL;
|
|
71
|
+
|
|
72
|
+
results.push(
|
|
73
|
+
this.benchmark('Logger creation (default format)', 1000, () => {
|
|
74
|
+
createLogger(`module-${Math.random()}`);
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Test 2: Logger creation with JSON format
|
|
79
|
+
resetLoggerInitialization();
|
|
80
|
+
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
81
|
+
|
|
82
|
+
results.push(
|
|
83
|
+
this.benchmark('Logger creation (JSON format)', 1000, () => {
|
|
84
|
+
createLogger(`module-json-${Math.random()}`);
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Test 3: Logging performance - default format
|
|
89
|
+
resetLoggerInitialization();
|
|
90
|
+
delete process.env.UNCHAINED_LOG_FORMAT;
|
|
91
|
+
const defaultLogger = createLogger('benchmark');
|
|
92
|
+
|
|
93
|
+
results.push(
|
|
94
|
+
this.benchmark('Log info (default format)', 100000, () => {
|
|
95
|
+
defaultLogger.info('Test message');
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Test 4: Logging performance - JSON format
|
|
100
|
+
resetLoggerInitialization();
|
|
101
|
+
process.env.UNCHAINED_LOG_FORMAT = 'json';
|
|
102
|
+
const jsonLogger = createLogger('benchmark-json');
|
|
103
|
+
|
|
104
|
+
results.push(
|
|
105
|
+
this.benchmark('Log info (JSON format)', 100000, () => {
|
|
106
|
+
jsonLogger.info('Test message', { id: 123 });
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Test 5: Complex object logging
|
|
111
|
+
const complexObject = {
|
|
112
|
+
user: { id: 'user123', name: 'John Doe', roles: ['admin', 'user'] },
|
|
113
|
+
action: 'purchase',
|
|
114
|
+
items: [
|
|
115
|
+
{ id: 'item1', name: 'Product A', price: 99.99, quantity: 2 },
|
|
116
|
+
{ id: 'item2', name: 'Product B', price: 49.99, quantity: 1 },
|
|
117
|
+
],
|
|
118
|
+
metadata: {
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
source: 'web',
|
|
121
|
+
version: '1.0.0',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
results.push(
|
|
126
|
+
this.benchmark('Log complex object (JSON)', 10000, () => {
|
|
127
|
+
jsonLogger.info('Complex operation', complexObject);
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Test 6: Debug logging when enabled
|
|
132
|
+
resetLoggerInitialization();
|
|
133
|
+
delete process.env.UNCHAINED_LOG_FORMAT;
|
|
134
|
+
process.env.DEBUG = 'benchmark-debug';
|
|
135
|
+
const debugEnabledLogger = createLogger('benchmark-debug');
|
|
136
|
+
|
|
137
|
+
results.push(
|
|
138
|
+
this.benchmark('Debug log (enabled)', 100000, () => {
|
|
139
|
+
debugEnabledLogger.debug('Debug message');
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Test 7: Debug logging when disabled
|
|
144
|
+
resetLoggerInitialization();
|
|
145
|
+
process.env.DEBUG = 'other-module';
|
|
146
|
+
const debugDisabledLogger = createLogger('benchmark-no-debug');
|
|
147
|
+
|
|
148
|
+
results.push(
|
|
149
|
+
this.benchmark('Debug log (disabled)', 100000, () => {
|
|
150
|
+
debugDisabledLogger.debug('Debug message');
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Test 8: Pattern matching performance
|
|
155
|
+
resetLoggerInitialization();
|
|
156
|
+
process.env.DEBUG = 'app:*,!app:excluded,special-*,test:module:*';
|
|
157
|
+
const testModules = [
|
|
158
|
+
'app:users',
|
|
159
|
+
'app:excluded',
|
|
160
|
+
'special-feature',
|
|
161
|
+
'test:module:sub',
|
|
162
|
+
'other:module',
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
results.push(
|
|
166
|
+
this.benchmark('Pattern matching (createLogger)', 5000, () => {
|
|
167
|
+
const module = testModules[Math.floor(Math.random() * testModules.length)];
|
|
168
|
+
createLogger(module);
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
this.restoreConsole();
|
|
173
|
+
this.printResults(results);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private printResults(results: BenchmarkResult[]) {
|
|
177
|
+
console.log('\n📊 Benchmark Results:\n');
|
|
178
|
+
|
|
179
|
+
// Header
|
|
180
|
+
const cols = {
|
|
181
|
+
name: 'Test Name',
|
|
182
|
+
ops: 'Ops/sec',
|
|
183
|
+
avgTime: 'Avg Time',
|
|
184
|
+
total: 'Total Time',
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const widths = {
|
|
188
|
+
name: Math.max(cols.name.length, ...results.map((r) => r.name.length)),
|
|
189
|
+
ops: 12,
|
|
190
|
+
avgTime: 12,
|
|
191
|
+
total: 12,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Print header
|
|
195
|
+
console.log(
|
|
196
|
+
`${cols.name.padEnd(widths.name)} | ${cols.ops.padStart(widths.ops)} | ${cols.avgTime.padStart(
|
|
197
|
+
widths.avgTime,
|
|
198
|
+
)} | ${cols.total.padStart(widths.total)}`,
|
|
199
|
+
);
|
|
200
|
+
console.log(
|
|
201
|
+
`${'-'.repeat(widths.name)}-|-${'-'.repeat(widths.ops)}-|-${'-'.repeat(widths.avgTime)}-|-${'-'.repeat(widths.total)}`,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Print results
|
|
205
|
+
for (const result of results) {
|
|
206
|
+
const opsPerSec = this.formatNumber(result.opsPerSecond);
|
|
207
|
+
const avgTime = this.formatTime(result.avgTimeMs);
|
|
208
|
+
const totalTime = `${result.totalTime.toFixed(2)} ms`;
|
|
209
|
+
|
|
210
|
+
console.log(
|
|
211
|
+
`${result.name.padEnd(widths.name)} | ${opsPerSec.padStart(widths.ops)} | ${avgTime.padStart(
|
|
212
|
+
widths.avgTime,
|
|
213
|
+
)} | ${totalTime.padStart(widths.total)}`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Performance insights
|
|
218
|
+
console.log('\n💡 Performance Insights:\n');
|
|
219
|
+
|
|
220
|
+
// Compare format performance
|
|
221
|
+
const defaultLog = results.find((r) => r.name === 'Log info (default format)');
|
|
222
|
+
const jsonLog = results.find((r) => r.name === 'Log info (JSON format)');
|
|
223
|
+
if (defaultLog && jsonLog) {
|
|
224
|
+
const diff = ((jsonLog.avgTimeMs - defaultLog.avgTimeMs) / defaultLog.avgTimeMs) * 100;
|
|
225
|
+
console.log(
|
|
226
|
+
`• JSON format logging is ${Math.abs(diff).toFixed(1)}% ${diff > 0 ? 'slower' : 'faster'} than default format`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Compare debug enabled vs disabled
|
|
231
|
+
const debugEnabled = results.find((r) => r.name === 'Debug log (enabled)');
|
|
232
|
+
const debugDisabled = results.find((r) => r.name === 'Debug log (disabled)');
|
|
233
|
+
if (debugEnabled && debugDisabled) {
|
|
234
|
+
const speedup = debugEnabled.avgTimeMs / debugDisabled.avgTimeMs;
|
|
235
|
+
console.log(
|
|
236
|
+
`• Debug logging when disabled is ${speedup.toFixed(1)}x faster (skips log processing entirely)`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Find fastest and slowest
|
|
241
|
+
const sorted = [...results].sort((a, b) => b.opsPerSecond - a.opsPerSecond);
|
|
242
|
+
console.log(
|
|
243
|
+
`• Fastest operation: ${sorted[0].name} (${this.formatNumber(sorted[0].opsPerSecond)} ops/s)`,
|
|
244
|
+
);
|
|
245
|
+
console.log(
|
|
246
|
+
`• Slowest operation: ${sorted[sorted.length - 1].name} (${this.formatNumber(
|
|
247
|
+
sorted[sorted.length - 1].opsPerSecond,
|
|
248
|
+
)} ops/s)`,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Memory note
|
|
252
|
+
console.log(
|
|
253
|
+
`\n📝 Note: These benchmarks measure execution speed. Memory usage and garbage collection`,
|
|
254
|
+
);
|
|
255
|
+
console.log(` impact are not measured but may be important factors in production environments.`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private formatNumber(num: number): string {
|
|
259
|
+
if (num >= 1000000) return `${(num / 1000000).toFixed(2)}M`;
|
|
260
|
+
if (num >= 1000) return `${(num / 1000).toFixed(2)}K`;
|
|
261
|
+
return num.toFixed(0);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private formatTime(ms: number): string {
|
|
265
|
+
if (ms < 0.001) return `${(ms * 1000).toFixed(2)} µs`;
|
|
266
|
+
if (ms < 1) return `${ms.toFixed(3)} ms`;
|
|
267
|
+
return `${ms.toFixed(2)} ms`;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Run benchmark
|
|
272
|
+
const benchmark = new LoggerBenchmark();
|
|
273
|
+
benchmark.run().catch(console.error);
|
package/lib/createLogger.d.ts
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export interface Logger {
|
|
2
|
+
trace: (message: any, ...args: any[]) => void;
|
|
3
|
+
debug: (message: any, ...args: any[]) => void;
|
|
4
|
+
info: (message: any, ...args: any[]) => void;
|
|
5
|
+
warn: (message: any, ...args: any[]) => void;
|
|
6
|
+
error: (message: any, ...args: any[]) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resets all internal caches. Used for testing to ensure a clean state.
|
|
10
|
+
*/
|
|
11
|
+
export declare const resetLoggerInitialization: () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a logger instance for the specified module name.
|
|
14
|
+
* The logger respects DEBUG, LOG_LEVEL, and UNCHAINED_LOG_FORMAT environment variables.
|
|
15
|
+
*
|
|
16
|
+
* Performance optimizations:
|
|
17
|
+
* - Returns no-op functions for disabled log levels (zero-cost logging)
|
|
18
|
+
* - Caches regex patterns and debug results for fast pattern matching
|
|
19
|
+
*
|
|
20
|
+
* @param moduleName - The name of the module (used for filtering and display)
|
|
21
|
+
* @returns A logger instance with trace, debug, info, warn, and error methods
|
|
22
|
+
*/
|
|
23
|
+
export declare const createLogger: (moduleName: string) => Logger;
|
|
3
24
|
//# sourceMappingURL=createLogger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createLogger.d.ts","sourceRoot":"","sources":["../src/createLogger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createLogger.d.ts","sourceRoot":"","sources":["../src/createLogger.ts"],"names":[],"mappings":"AA8FA,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC9C,KAAK,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC9C,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC7C,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAC7C,KAAK,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CAC/C;AAyBD;;GAEG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAG5C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAI,YAAY,MAAM,KAAG,MAmFjD,CAAC"}
|
package/lib/createLogger.js
CHANGED
|
@@ -1,74 +1,199 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
1
2
|
import { stringify } from 'safe-stable-stringify';
|
|
2
|
-
import { default as log } from 'loglevel';
|
|
3
3
|
import { LogLevel } from './logger.types.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Performance optimization: Cache compiled regex patterns to avoid recreating them
|
|
6
|
+
* on every DEBUG pattern match. This provides ~190% improvement in pattern matching.
|
|
7
|
+
*/
|
|
8
|
+
const regexCache = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Performance optimization: Cache DEBUG pattern matching results per module
|
|
11
|
+
* to avoid recomputation for the same module names.
|
|
12
|
+
*/
|
|
13
|
+
const debugPatternCache = new Map();
|
|
14
|
+
/**
|
|
15
|
+
* Checks if a module name matches the DEBUG environment variable pattern.
|
|
16
|
+
* Supports wildcards (*), exclusions (-pattern), and comma-separated lists.
|
|
17
|
+
* Results are cached for performance.
|
|
18
|
+
*/
|
|
7
19
|
const debugStringContainsModule = (debugString, moduleName) => {
|
|
8
20
|
if (!debugString)
|
|
9
21
|
return false;
|
|
22
|
+
// Check cache first for performance
|
|
23
|
+
const cacheKey = `${debugString}::${moduleName}`;
|
|
24
|
+
const cached = debugPatternCache.get(cacheKey);
|
|
25
|
+
if (cached !== undefined)
|
|
26
|
+
return cached;
|
|
10
27
|
const loggingMatched = debugString.split(',').reduce((accumulator, name) => {
|
|
11
28
|
if (accumulator === false)
|
|
12
29
|
return accumulator;
|
|
13
|
-
|
|
14
|
-
|
|
30
|
+
// Get or create cached regex pattern
|
|
31
|
+
let regExp = regexCache.get(name);
|
|
32
|
+
if (!regExp) {
|
|
33
|
+
const nameRegex = name.replace(/-/i, '\\-?').replace(/:\*/i, '\\:?*').replace(/\*/i, '.*');
|
|
34
|
+
regExp = new RegExp(`^${nameRegex}$`, 'm');
|
|
35
|
+
regexCache.set(name, regExp);
|
|
36
|
+
}
|
|
15
37
|
if (regExp.test(moduleName)) {
|
|
38
|
+
// Exclusion pattern (starts with -)
|
|
16
39
|
if (name.slice(0, 1) === '-') {
|
|
17
|
-
// explicitly disable
|
|
18
40
|
return false;
|
|
19
41
|
}
|
|
20
42
|
return true;
|
|
21
43
|
}
|
|
22
44
|
return accumulator;
|
|
23
45
|
}, undefined);
|
|
24
|
-
|
|
46
|
+
const result = loggingMatched || false;
|
|
47
|
+
debugPatternCache.set(cacheKey, result);
|
|
48
|
+
return result;
|
|
25
49
|
};
|
|
50
|
+
// ANSI color codes
|
|
26
51
|
const colors = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
gray: '\x1b[90m',
|
|
53
|
+
green: '\x1b[32m',
|
|
54
|
+
cyan: '\x1b[36m',
|
|
55
|
+
blue: '\x1b[34m',
|
|
56
|
+
yellow: '\x1b[33m',
|
|
57
|
+
red: '\x1b[31m',
|
|
58
|
+
magenta: '\x1b[35m',
|
|
59
|
+
reset: '\x1b[0m',
|
|
32
60
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
// Log level configuration
|
|
62
|
+
var LogLevelValue;
|
|
63
|
+
(function (LogLevelValue) {
|
|
64
|
+
LogLevelValue[LogLevelValue["TRACE"] = 0] = "TRACE";
|
|
65
|
+
LogLevelValue[LogLevelValue["DEBUG"] = 1] = "DEBUG";
|
|
66
|
+
LogLevelValue[LogLevelValue["INFO"] = 2] = "INFO";
|
|
67
|
+
LogLevelValue[LogLevelValue["WARN"] = 3] = "WARN";
|
|
68
|
+
LogLevelValue[LogLevelValue["ERROR"] = 4] = "ERROR";
|
|
69
|
+
})(LogLevelValue || (LogLevelValue = {}));
|
|
70
|
+
const logLevelMap = {
|
|
71
|
+
[LogLevel.Verbose]: LogLevelValue.TRACE,
|
|
72
|
+
[LogLevel.Debug]: LogLevelValue.DEBUG,
|
|
73
|
+
[LogLevel.Info]: LogLevelValue.INFO,
|
|
74
|
+
[LogLevel.Warning]: LogLevelValue.WARN,
|
|
75
|
+
[LogLevel.Error]: LogLevelValue.ERROR,
|
|
76
|
+
// Add trace as an alias for verbose
|
|
77
|
+
trace: LogLevelValue.TRACE,
|
|
78
|
+
};
|
|
79
|
+
const levelColors = {
|
|
80
|
+
trace: colors.magenta,
|
|
81
|
+
debug: colors.cyan,
|
|
82
|
+
info: colors.blue,
|
|
83
|
+
warn: colors.yellow,
|
|
84
|
+
error: colors.red,
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Formats the current time as HH:MM:SS for log output.
|
|
88
|
+
* Optimized to avoid unnecessary string conversions.
|
|
89
|
+
*/
|
|
90
|
+
const formatTimestamp = () => {
|
|
91
|
+
const now = new Date();
|
|
92
|
+
const hours = now.getHours().toString().padStart(2, '0');
|
|
93
|
+
const minutes = now.getMinutes().toString().padStart(2, '0');
|
|
94
|
+
const seconds = now.getSeconds().toString().padStart(2, '0');
|
|
95
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Custom JSON replacer function that handles BigInt values by converting them to strings.
|
|
99
|
+
* This ensures BigInt values can be serialized in JSON logs.
|
|
100
|
+
*/
|
|
101
|
+
const bigintReplacer = (_key, value) => {
|
|
102
|
+
if (typeof value === 'bigint') {
|
|
103
|
+
return value.toString();
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Resets all internal caches. Used for testing to ensure a clean state.
|
|
109
|
+
*/
|
|
110
|
+
export const resetLoggerInitialization = () => {
|
|
111
|
+
regexCache.clear();
|
|
112
|
+
debugPatternCache.clear();
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Creates a logger instance for the specified module name.
|
|
116
|
+
* The logger respects DEBUG, LOG_LEVEL, and UNCHAINED_LOG_FORMAT environment variables.
|
|
117
|
+
*
|
|
118
|
+
* Performance optimizations:
|
|
119
|
+
* - Returns no-op functions for disabled log levels (zero-cost logging)
|
|
120
|
+
* - Caches regex patterns and debug results for fast pattern matching
|
|
121
|
+
*
|
|
122
|
+
* @param moduleName - The name of the module (used for filtering and display)
|
|
123
|
+
* @returns A logger instance with trace, debug, info, warn, and error methods
|
|
124
|
+
*/
|
|
62
125
|
export const createLogger = (moduleName) => {
|
|
126
|
+
const { DEBUG = '', LOG_LEVEL = LogLevel.Info, UNCHAINED_LOG_FORMAT = 'unchained' } = process.env;
|
|
127
|
+
// Validate format at logger creation time
|
|
128
|
+
const format = UNCHAINED_LOG_FORMAT.toLowerCase();
|
|
129
|
+
if (format !== 'json' && format !== 'unchained') {
|
|
130
|
+
throw new Error(`UNCHAINED_LOG_FORMAT is invalid, use one of json,unchained`);
|
|
131
|
+
}
|
|
132
|
+
// Determine minimum log level
|
|
63
133
|
const loggingMatched = debugStringContainsModule(DEBUG, moduleName);
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
134
|
+
const logLevelLower = LOG_LEVEL.toLowerCase();
|
|
135
|
+
const mappedLevel = logLevelMap[logLevelLower];
|
|
136
|
+
const minLevel = loggingMatched
|
|
137
|
+
? LogLevelValue.DEBUG
|
|
138
|
+
: mappedLevel !== undefined
|
|
139
|
+
? mappedLevel
|
|
140
|
+
: LogLevelValue.INFO;
|
|
141
|
+
// Performance optimization: No-op function for disabled log levels
|
|
142
|
+
const noop = () => {
|
|
143
|
+
// Intentionally empty for performance
|
|
144
|
+
};
|
|
145
|
+
const log = (level, levelValue, message, ...args) => {
|
|
146
|
+
if (levelValue < minLevel)
|
|
147
|
+
return;
|
|
148
|
+
if (format === 'json') {
|
|
149
|
+
// JSON format
|
|
150
|
+
const logObject = {
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
level: level.toUpperCase(),
|
|
153
|
+
name: moduleName,
|
|
154
|
+
message: typeof message === 'string' ? message : message,
|
|
155
|
+
};
|
|
156
|
+
// Merge additional args if they're objects, guarding against prototype pollution
|
|
157
|
+
if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) {
|
|
158
|
+
for (const key in args[0]) {
|
|
159
|
+
// Skip prototype pollution vectors
|
|
160
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
|
|
161
|
+
continue;
|
|
162
|
+
logObject[key] = args[0][key];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Use safe-stable-stringify for proper JSON output with BigInt support
|
|
166
|
+
console.log(stringify(logObject, bigintReplacer));
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Unchained format (pretty)
|
|
170
|
+
const timestamp = formatTimestamp();
|
|
171
|
+
const levelColor = levelColors[level] || colors.reset;
|
|
172
|
+
const prefix = `${colors.gray}${timestamp}${colors.reset} [${colors.green}${moduleName}${colors.reset}] ${levelColor}${level}:${colors.reset}`;
|
|
173
|
+
if (typeof message === 'string') {
|
|
174
|
+
console.log(prefix, message, ...args);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log(prefix, inspect(message, { colors: true, depth: 3 }), ...args);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
trace: LogLevelValue.TRACE < minLevel
|
|
183
|
+
? noop
|
|
184
|
+
: (message, ...args) => log('trace', LogLevelValue.TRACE, message, ...args),
|
|
185
|
+
debug: LogLevelValue.DEBUG < minLevel
|
|
186
|
+
? noop
|
|
187
|
+
: (message, ...args) => log('debug', LogLevelValue.DEBUG, message, ...args),
|
|
188
|
+
info: LogLevelValue.INFO < minLevel
|
|
189
|
+
? noop
|
|
190
|
+
: (message, ...args) => log('info', LogLevelValue.INFO, message, ...args),
|
|
191
|
+
warn: LogLevelValue.WARN < minLevel
|
|
192
|
+
? noop
|
|
193
|
+
: (message, ...args) => log('warn', LogLevelValue.WARN, message, ...args),
|
|
194
|
+
error: LogLevelValue.ERROR < minLevel
|
|
195
|
+
? noop
|
|
196
|
+
: (message, ...args) => log('error', LogLevelValue.ERROR, message, ...args),
|
|
70
197
|
};
|
|
71
|
-
logger.setDefaultLevel(loggingMatched ? log.levels.DEBUG : logLevelMap[LOG_LEVEL.toLowerCase()]);
|
|
72
|
-
return logger;
|
|
73
198
|
};
|
|
74
199
|
//# sourceMappingURL=createLogger.js.map
|