@unchainedshop/logger 4.8.29 โ 5.0.0-alpha.10
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 +1 -1
- package/lib/createLogger.d.ts +2 -2
- package/lib/createLogger.js +20 -26
- package/package.json +5 -2
- package/benchmarks/README.md +0 -32
- package/benchmarks/benchmark.ts +0 -273
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A high-performance, feature-rich logging package for Unchained Engine with suppo
|
|
|
9
9
|
- ๐ **Debug Patterns**: Flexible DEBUG environment variable with wildcards and exclusions
|
|
10
10
|
- ๐ **Log Levels**: Five log levels (trace, debug, info, warn, error) with environment-based filtering
|
|
11
11
|
- ๐ช **TypeScript**: Full TypeScript support with type definitions
|
|
12
|
-
- ๐ง **Zero Dependencies**:
|
|
12
|
+
- ๐ง **Zero Runtime Dependencies**: Uses Node.js built-ins
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
package/lib/createLogger.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export type LogContextProvider = () => Record<string, unknown> | undefined | null;
|
|
2
|
-
export declare const setLogContextProvider: (provider: LogContextProvider | null) => void;
|
|
3
1
|
export interface Logger {
|
|
4
2
|
trace: (message: any, ...args: any[]) => void;
|
|
5
3
|
debug: (message: any, ...args: any[]) => void;
|
|
@@ -7,5 +5,7 @@ export interface Logger {
|
|
|
7
5
|
warn: (message: any, ...args: any[]) => void;
|
|
8
6
|
error: (message: any, ...args: any[]) => void;
|
|
9
7
|
}
|
|
8
|
+
export type LogContextProvider = () => Record<string, unknown> | undefined;
|
|
9
|
+
export declare const setLogContextProvider: (provider: LogContextProvider | undefined) => void;
|
|
10
10
|
export declare const resetLoggerInitialization: () => void;
|
|
11
11
|
export declare const createLogger: (moduleName: string) => Logger;
|
package/lib/createLogger.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
2
|
import { safeStringify } from "./safe-stringify.js";
|
|
3
3
|
import { LogLevel } from "./logger.types.js";
|
|
4
|
-
let currentContextProvider = null;
|
|
5
|
-
export const setLogContextProvider = (provider) => {
|
|
6
|
-
currentContextProvider = provider;
|
|
7
|
-
};
|
|
8
|
-
const RESERVED_KEYS = new Set(['timestamp', 'level', 'name', 'message']);
|
|
9
|
-
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
10
4
|
const regexCache = new Map();
|
|
11
5
|
const debugPatternCache = new Map();
|
|
12
6
|
const escapeRegexForDebug = (pattern) => {
|
|
@@ -93,10 +87,14 @@ const formatTimestamp = () => {
|
|
|
93
87
|
const seconds = now.getSeconds().toString().padStart(2, '0');
|
|
94
88
|
return `${hours}:${minutes}:${seconds}`;
|
|
95
89
|
};
|
|
90
|
+
let logContextProvider;
|
|
91
|
+
export const setLogContextProvider = (provider) => {
|
|
92
|
+
logContextProvider = provider;
|
|
93
|
+
};
|
|
96
94
|
export const resetLoggerInitialization = () => {
|
|
97
95
|
regexCache.clear();
|
|
98
96
|
debugPatternCache.clear();
|
|
99
|
-
|
|
97
|
+
logContextProvider = undefined;
|
|
100
98
|
};
|
|
101
99
|
export const createLogger = (moduleName) => {
|
|
102
100
|
const { DEBUG = '', LOG_LEVEL = LogLevel.Info, UNCHAINED_LOG_FORMAT = 'unchained' } = process.env;
|
|
@@ -124,31 +122,27 @@ export const createLogger = (moduleName) => {
|
|
|
124
122
|
name: moduleName,
|
|
125
123
|
message: typeof message === 'string' ? message : message,
|
|
126
124
|
};
|
|
127
|
-
if (currentContextProvider) {
|
|
128
|
-
let context;
|
|
129
|
-
try {
|
|
130
|
-
context = currentContextProvider();
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
context = undefined;
|
|
134
|
-
}
|
|
135
|
-
if (context && typeof context === 'object') {
|
|
136
|
-
for (const key in context) {
|
|
137
|
-
if (UNSAFE_KEYS.has(key) || RESERVED_KEYS.has(key))
|
|
138
|
-
continue;
|
|
139
|
-
if (key in logObject)
|
|
140
|
-
continue;
|
|
141
|
-
logObject[key] = context[key];
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
125
|
if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) {
|
|
146
126
|
for (const key in args[0]) {
|
|
147
|
-
if (
|
|
127
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
|
|
148
128
|
continue;
|
|
149
129
|
logObject[key] = args[0][key];
|
|
150
130
|
}
|
|
151
131
|
}
|
|
132
|
+
if (logContextProvider) {
|
|
133
|
+
try {
|
|
134
|
+
const extra = logContextProvider();
|
|
135
|
+
if (extra) {
|
|
136
|
+
for (const key in extra) {
|
|
137
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
|
|
138
|
+
continue;
|
|
139
|
+
logObject[key] = extra[key];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
}
|
|
145
|
+
}
|
|
152
146
|
console.log(safeStringify(logObject));
|
|
153
147
|
}
|
|
154
148
|
else {
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/logger",
|
|
3
3
|
"description": "High-performance logging package for the Unchained Engine",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.0.0-alpha.10",
|
|
5
|
+
"files": [
|
|
6
|
+
"lib"
|
|
7
|
+
],
|
|
5
8
|
"main": "lib/logger-index.js",
|
|
6
9
|
"types": "lib/logger-index.d.ts",
|
|
7
10
|
"type": "module",
|
|
@@ -38,6 +41,6 @@
|
|
|
38
41
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"@types/node": "^26.2.0",
|
|
41
|
-
"typescript": "^
|
|
44
|
+
"typescript": "^6.0.3"
|
|
42
45
|
}
|
|
43
46
|
}
|
package/benchmarks/README.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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
|
package/benchmarks/benchmark.ts
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { performance } from 'node:perf_hooks';
|
|
2
|
-
import { createLogger, resetLoggerInitialization } from '../src/createLogger.ts';
|
|
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);
|