@morojs/moro 1.5.3 → 1.5.5
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/dist/core/auth/morojs-adapter.js +23 -12
- package/dist/core/auth/morojs-adapter.js.map +1 -1
- package/dist/core/http/http-server.js +12 -8
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/logger/filters.js +12 -4
- package/dist/core/logger/filters.js.map +1 -1
- package/dist/core/logger/logger.d.ts +45 -0
- package/dist/core/logger/logger.js +579 -60
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/middleware/built-in/request-logger.js +4 -2
- package/dist/core/middleware/built-in/request-logger.js.map +1 -1
- package/dist/core/modules/auto-discovery.d.ts +1 -0
- package/dist/core/modules/auto-discovery.js +9 -5
- package/dist/core/modules/auto-discovery.js.map +1 -1
- package/dist/core/modules/modules.d.ts +1 -0
- package/dist/core/modules/modules.js +8 -2
- package/dist/core/modules/modules.js.map +1 -1
- package/dist/core/networking/adapters/ws-adapter.d.ts +1 -0
- package/dist/core/networking/adapters/ws-adapter.js +3 -1
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
- package/dist/core/networking/service-discovery.d.ts +1 -0
- package/dist/core/networking/service-discovery.js +23 -11
- package/dist/core/networking/service-discovery.js.map +1 -1
- package/dist/moro.d.ts +35 -0
- package/dist/moro.js +156 -25
- package/dist/moro.js.map +1 -1
- package/dist/types/logger.d.ts +3 -0
- package/package.json +1 -1
- package/src/core/auth/morojs-adapter.ts +25 -12
- package/src/core/database/README.md +26 -16
- package/src/core/http/http-server.ts +15 -12
- package/src/core/logger/filters.ts +12 -4
- package/src/core/logger/logger.ts +649 -62
- package/src/core/middleware/built-in/request-logger.ts +6 -2
- package/src/core/modules/auto-discovery.ts +13 -5
- package/src/core/modules/modules.ts +9 -5
- package/src/core/networking/adapters/ws-adapter.ts +3 -1
- package/src/core/networking/service-discovery.ts +23 -9
- package/src/moro.ts +200 -28
- package/src/types/logger.ts +3 -0
|
@@ -24,6 +24,31 @@ class MoroLogger {
|
|
|
24
24
|
contextPrefix;
|
|
25
25
|
contextMetadata;
|
|
26
26
|
parent; // Reference to parent logger for level inheritance
|
|
27
|
+
// Performance optimizations
|
|
28
|
+
historyIndex = 0;
|
|
29
|
+
historySize = 0;
|
|
30
|
+
lastMemoryCheck = 0;
|
|
31
|
+
memoryCheckInterval = 5000; // 5 seconds
|
|
32
|
+
cachedTimestamp = '';
|
|
33
|
+
lastTimestamp = 0;
|
|
34
|
+
timestampCacheInterval = 100; // 100ms for better precision
|
|
35
|
+
// Object pooling for LogEntry objects (Pino's technique)
|
|
36
|
+
static ENTRY_POOL = [];
|
|
37
|
+
static MAX_POOL_SIZE = 100;
|
|
38
|
+
static poolIndex = 0;
|
|
39
|
+
// String builder for efficient concatenation
|
|
40
|
+
static stringBuilder = [];
|
|
41
|
+
static stringBuilderIndex = 0;
|
|
42
|
+
// Buffered output for performance
|
|
43
|
+
outputBuffer = [];
|
|
44
|
+
bufferSize = 0;
|
|
45
|
+
maxBufferSize = 1000;
|
|
46
|
+
flushTimeout = null;
|
|
47
|
+
flushInterval = 1; // 1ms micro-batching
|
|
48
|
+
// Buffer overflow protection
|
|
49
|
+
bufferOverflowThreshold;
|
|
50
|
+
emergencyFlushInProgress = false;
|
|
51
|
+
// High-performance output methods
|
|
27
52
|
static LEVELS = {
|
|
28
53
|
debug: 0,
|
|
29
54
|
info: 1,
|
|
@@ -41,11 +66,20 @@ class MoroLogger {
|
|
|
41
66
|
context: '\x1b[34m', // Blue
|
|
42
67
|
metadata: '\x1b[37m', // White
|
|
43
68
|
performance: '\x1b[36m', // Cyan
|
|
69
|
+
reset: '\x1b[0m', // Reset
|
|
44
70
|
};
|
|
45
71
|
static RESET = '\x1b[0m';
|
|
46
72
|
static BOLD = '\x1b[1m';
|
|
73
|
+
// Static pre-allocated strings for performance
|
|
74
|
+
static LEVEL_STRINGS = {
|
|
75
|
+
debug: 'DEBUG',
|
|
76
|
+
info: 'INFO ',
|
|
77
|
+
warn: 'WARN ',
|
|
78
|
+
error: 'ERROR',
|
|
79
|
+
fatal: 'FATAL',
|
|
80
|
+
};
|
|
47
81
|
constructor(options = {}) {
|
|
48
|
-
this.options = {
|
|
82
|
+
this.options = this.validateOptions({
|
|
49
83
|
level: 'info',
|
|
50
84
|
enableColors: true,
|
|
51
85
|
enableTimestamp: true,
|
|
@@ -56,9 +90,14 @@ class MoroLogger {
|
|
|
56
90
|
outputs: [],
|
|
57
91
|
filters: [],
|
|
58
92
|
maxEntries: 1000,
|
|
93
|
+
maxBufferSize: 1000,
|
|
59
94
|
...options,
|
|
60
|
-
};
|
|
95
|
+
});
|
|
61
96
|
this.level = this.options.level || 'info';
|
|
97
|
+
// Initialize buffer size from options
|
|
98
|
+
this.maxBufferSize = this.options.maxBufferSize || 1000;
|
|
99
|
+
// Initialize buffer overflow protection
|
|
100
|
+
this.bufferOverflowThreshold = this.maxBufferSize * 2;
|
|
62
101
|
// Add default console output
|
|
63
102
|
this.addOutput({
|
|
64
103
|
name: 'console',
|
|
@@ -69,6 +108,52 @@ class MoroLogger {
|
|
|
69
108
|
this.options.outputs?.forEach(output => this.addOutput(output));
|
|
70
109
|
this.options.filters?.forEach(filter => this.addFilter(filter));
|
|
71
110
|
}
|
|
111
|
+
// Object pooling methods
|
|
112
|
+
static getPooledEntry() {
|
|
113
|
+
if (MoroLogger.ENTRY_POOL.length > 0) {
|
|
114
|
+
const entry = MoroLogger.ENTRY_POOL.pop();
|
|
115
|
+
// Properly reset ALL properties to prevent memory leaks
|
|
116
|
+
entry.timestamp = new Date();
|
|
117
|
+
entry.level = 'info';
|
|
118
|
+
entry.message = '';
|
|
119
|
+
entry.context = undefined;
|
|
120
|
+
entry.metadata = undefined;
|
|
121
|
+
entry.performance = undefined;
|
|
122
|
+
entry.moduleId = undefined;
|
|
123
|
+
return entry;
|
|
124
|
+
}
|
|
125
|
+
return MoroLogger.createFreshEntry();
|
|
126
|
+
}
|
|
127
|
+
// ADD this new method:
|
|
128
|
+
static createFreshEntry() {
|
|
129
|
+
return {
|
|
130
|
+
timestamp: new Date(),
|
|
131
|
+
level: 'info',
|
|
132
|
+
message: '',
|
|
133
|
+
context: undefined,
|
|
134
|
+
metadata: undefined,
|
|
135
|
+
performance: undefined,
|
|
136
|
+
moduleId: undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
static returnPooledEntry(entry) {
|
|
140
|
+
if (MoroLogger.ENTRY_POOL.length < MoroLogger.MAX_POOL_SIZE) {
|
|
141
|
+
MoroLogger.ENTRY_POOL.push(entry);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// String builder methods
|
|
145
|
+
static resetStringBuilder() {
|
|
146
|
+
MoroLogger.stringBuilder.length = 0;
|
|
147
|
+
MoroLogger.stringBuilderIndex = 0;
|
|
148
|
+
}
|
|
149
|
+
static appendToBuilder(str) {
|
|
150
|
+
MoroLogger.stringBuilder[MoroLogger.stringBuilderIndex++] = str;
|
|
151
|
+
}
|
|
152
|
+
static buildString() {
|
|
153
|
+
const result = MoroLogger.stringBuilder.join('');
|
|
154
|
+
MoroLogger.resetStringBuilder();
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
72
157
|
debug(message, context, metadata) {
|
|
73
158
|
this.log('debug', message, context, metadata);
|
|
74
159
|
}
|
|
@@ -128,8 +213,44 @@ class MoroLogger {
|
|
|
128
213
|
this.filters.delete(name);
|
|
129
214
|
}
|
|
130
215
|
getHistory(count) {
|
|
131
|
-
|
|
132
|
-
|
|
216
|
+
if (this.historySize === 0)
|
|
217
|
+
return [];
|
|
218
|
+
if (this.historySize < (this.options.maxEntries || 1000)) {
|
|
219
|
+
// History not full yet, return all entries
|
|
220
|
+
const entries = this.history.slice(0, this.historySize);
|
|
221
|
+
return count ? entries.slice(-count) : entries;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
// History is full, use circular buffer logic
|
|
225
|
+
const entries = [];
|
|
226
|
+
const maxEntries = this.options.maxEntries || 1000;
|
|
227
|
+
for (let i = 0; i < maxEntries; i++) {
|
|
228
|
+
const index = (this.historyIndex + i) % maxEntries;
|
|
229
|
+
if (this.history[index]) {
|
|
230
|
+
entries.push(this.history[index]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return count ? entries.slice(-count) : entries;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Cached timestamp formatting to avoid repeated string operations
|
|
237
|
+
getCachedTimestamp(timestamp) {
|
|
238
|
+
const now = timestamp.getTime();
|
|
239
|
+
if (now - this.lastTimestamp > this.timestampCacheInterval) {
|
|
240
|
+
this.lastTimestamp = now;
|
|
241
|
+
this.cachedTimestamp = timestamp.toISOString().replace('T', ' ').slice(0, 19);
|
|
242
|
+
}
|
|
243
|
+
return this.cachedTimestamp;
|
|
244
|
+
}
|
|
245
|
+
// Cached timestamp generation (updates once per second)
|
|
246
|
+
getFastCachedTimestamp() {
|
|
247
|
+
const now = Date.now();
|
|
248
|
+
if (now - this.lastTimestamp > 1000) {
|
|
249
|
+
// Update every second
|
|
250
|
+
this.lastTimestamp = now;
|
|
251
|
+
this.cachedTimestamp = new Date(now).toISOString().slice(0, 19).replace('T', ' ');
|
|
252
|
+
}
|
|
253
|
+
return this.cachedTimestamp;
|
|
133
254
|
}
|
|
134
255
|
getMetrics() {
|
|
135
256
|
const now = Date.now();
|
|
@@ -155,53 +276,131 @@ class MoroLogger {
|
|
|
155
276
|
memoryUsage: 0,
|
|
156
277
|
};
|
|
157
278
|
}
|
|
279
|
+
// Optimized logging method
|
|
158
280
|
log(level, message, context, metadata) {
|
|
159
|
-
//
|
|
281
|
+
// Quick level check - use parent level if available (for child loggers)
|
|
160
282
|
const effectiveLevel = this.parent ? this.parent.level : this.level;
|
|
161
283
|
if (MoroLogger.LEVELS[level] < MoroLogger.LEVELS[effectiveLevel]) {
|
|
162
284
|
return;
|
|
163
285
|
}
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
286
|
+
// Absolute minimal path for simple logs - pure speed
|
|
287
|
+
if (!metadata && !context && !this.contextPrefix && !this.contextMetadata) {
|
|
288
|
+
this.writeSimpleLog(level, message);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Minimal path for logs with context but no metadata
|
|
292
|
+
if (!metadata && !this.contextMetadata) {
|
|
293
|
+
this.writeSimpleLog(level, message, context);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Path for complex logs
|
|
297
|
+
if (metadata && Object.keys(metadata).length > 0) {
|
|
298
|
+
this.complexLog(level, message, context, metadata);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Full logging path for complex logs
|
|
302
|
+
this.fullLog(level, message, context, metadata);
|
|
303
|
+
}
|
|
304
|
+
// Full logging with all features
|
|
305
|
+
fullLog(level, message, context, metadata) {
|
|
306
|
+
// Use object pooling for LogEntry (Pino's technique)
|
|
307
|
+
const entry = MoroLogger.getPooledEntry();
|
|
308
|
+
const now = Date.now();
|
|
309
|
+
entry.timestamp = new Date(now);
|
|
310
|
+
entry.level = level;
|
|
311
|
+
entry.message = message;
|
|
312
|
+
entry.context = this.contextPrefix
|
|
313
|
+
? context
|
|
314
|
+
? `${this.contextPrefix}:${context}`
|
|
315
|
+
: this.contextPrefix
|
|
316
|
+
: context;
|
|
317
|
+
entry.metadata = this.createMetadata(metadata);
|
|
318
|
+
entry.performance = this.options.enablePerformance ? this.getPerformanceData(now) : undefined;
|
|
319
|
+
// Apply filters with early return optimization
|
|
320
|
+
if (this.filters.size > 0) {
|
|
321
|
+
for (const filter of this.filters.values()) {
|
|
322
|
+
if (!filter.filter(entry)) {
|
|
323
|
+
MoroLogger.returnPooledEntry(entry);
|
|
324
|
+
return;
|
|
178
325
|
}
|
|
179
|
-
: undefined,
|
|
180
|
-
};
|
|
181
|
-
// Apply filters
|
|
182
|
-
for (const filter of this.filters.values()) {
|
|
183
|
-
if (!filter.filter(entry)) {
|
|
184
|
-
return;
|
|
185
326
|
}
|
|
186
327
|
}
|
|
187
328
|
// Update metrics
|
|
188
329
|
this.updateMetrics(entry);
|
|
189
|
-
// Store in history
|
|
190
|
-
this.
|
|
191
|
-
|
|
192
|
-
|
|
330
|
+
// Store in history with circular buffer optimization
|
|
331
|
+
this.addToHistory(entry);
|
|
332
|
+
// Write to outputs with batched processing
|
|
333
|
+
this.writeToOutputs(entry, level);
|
|
334
|
+
// Return entry to pool
|
|
335
|
+
MoroLogger.returnPooledEntry(entry);
|
|
336
|
+
}
|
|
337
|
+
// Absolute minimal logging - pure speed, no overhead
|
|
338
|
+
complexLog(level, message, context, metadata) {
|
|
339
|
+
// Use object pooling for LogEntry (Pino's technique)
|
|
340
|
+
const entry = MoroLogger.getPooledEntry();
|
|
341
|
+
const now = Date.now();
|
|
342
|
+
entry.timestamp = new Date(now);
|
|
343
|
+
entry.level = level;
|
|
344
|
+
entry.message = message;
|
|
345
|
+
entry.context = this.contextPrefix
|
|
346
|
+
? context
|
|
347
|
+
? `${this.contextPrefix}:${context}`
|
|
348
|
+
: this.contextPrefix
|
|
349
|
+
: context;
|
|
350
|
+
entry.metadata = this.createMetadata(metadata);
|
|
351
|
+
entry.performance = this.options.enablePerformance ? this.getPerformanceData(now) : undefined;
|
|
352
|
+
// Write to outputs with batched processing
|
|
353
|
+
this.writeToOutputs(entry, level);
|
|
354
|
+
// Return entry to pool
|
|
355
|
+
MoroLogger.returnPooledEntry(entry);
|
|
356
|
+
}
|
|
357
|
+
// Simple log writer with colors for minimal overhead cases
|
|
358
|
+
writeSimpleLog(level, message, context) {
|
|
359
|
+
const colors = this.options.enableColors !== false;
|
|
360
|
+
const levelReset = colors ? MoroLogger.RESET : '';
|
|
361
|
+
MoroLogger.resetStringBuilder();
|
|
362
|
+
// Timestamp with caching optimization
|
|
363
|
+
if (this.options.enableTimestamp !== false) {
|
|
364
|
+
const timestamp = this.getFastCachedTimestamp();
|
|
365
|
+
if (colors) {
|
|
366
|
+
MoroLogger.appendToBuilder(MoroLogger.COLORS.timestamp);
|
|
367
|
+
MoroLogger.appendToBuilder(timestamp);
|
|
368
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
MoroLogger.appendToBuilder(timestamp);
|
|
372
|
+
}
|
|
373
|
+
MoroLogger.appendToBuilder(' ');
|
|
193
374
|
}
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
375
|
+
// Level with pre-allocated strings
|
|
376
|
+
const levelStr = MoroLogger.LEVEL_STRINGS[level];
|
|
377
|
+
if (colors) {
|
|
378
|
+
MoroLogger.appendToBuilder(MoroLogger.COLORS[level]);
|
|
379
|
+
MoroLogger.appendToBuilder(MoroLogger.BOLD);
|
|
380
|
+
MoroLogger.appendToBuilder(levelStr);
|
|
381
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
MoroLogger.appendToBuilder(levelStr);
|
|
385
|
+
}
|
|
386
|
+
// Context
|
|
387
|
+
if (context && this.options.enableContext !== false) {
|
|
388
|
+
MoroLogger.appendToBuilder(' ');
|
|
389
|
+
if (colors) {
|
|
390
|
+
MoroLogger.appendToBuilder(MoroLogger.COLORS.context);
|
|
391
|
+
MoroLogger.appendToBuilder(`[${context}]`);
|
|
392
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
MoroLogger.appendToBuilder(`[${context}]`);
|
|
203
396
|
}
|
|
204
397
|
}
|
|
398
|
+
// Message
|
|
399
|
+
MoroLogger.appendToBuilder(' ');
|
|
400
|
+
MoroLogger.appendToBuilder(message);
|
|
401
|
+
// Output main log line with high-performance method
|
|
402
|
+
const finalMessage = MoroLogger.buildString();
|
|
403
|
+
this.output(`${finalMessage}\n`, level);
|
|
205
404
|
}
|
|
206
405
|
updateMetrics(entry) {
|
|
207
406
|
this.metrics.totalLogs++;
|
|
@@ -211,16 +410,79 @@ class MoroLogger {
|
|
|
211
410
|
(this.metrics.logsByContext[entry.context] || 0) + 1;
|
|
212
411
|
}
|
|
213
412
|
}
|
|
413
|
+
// Optimized metadata creation to avoid unnecessary object spreading
|
|
414
|
+
createMetadata(metadata) {
|
|
415
|
+
if (!metadata && !this.contextMetadata) {
|
|
416
|
+
return {};
|
|
417
|
+
}
|
|
418
|
+
if (!metadata) {
|
|
419
|
+
return { ...this.contextMetadata };
|
|
420
|
+
}
|
|
421
|
+
if (!this.contextMetadata) {
|
|
422
|
+
return { ...metadata };
|
|
423
|
+
}
|
|
424
|
+
return { ...this.contextMetadata, ...metadata };
|
|
425
|
+
}
|
|
426
|
+
// Optimized performance data with caching
|
|
427
|
+
getPerformanceData(now) {
|
|
428
|
+
if (now - this.lastMemoryCheck > this.memoryCheckInterval) {
|
|
429
|
+
this.lastMemoryCheck = now;
|
|
430
|
+
this.metrics.memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
|
|
431
|
+
}
|
|
432
|
+
return { memory: this.metrics.memoryUsage };
|
|
433
|
+
}
|
|
434
|
+
// Circular buffer implementation for history (O(1) instead of O(n))
|
|
435
|
+
addToHistory(entry) {
|
|
436
|
+
const maxEntries = this.options.maxEntries || 1000;
|
|
437
|
+
if (this.historySize < maxEntries) {
|
|
438
|
+
this.history[this.historySize] = entry;
|
|
439
|
+
this.historySize++;
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
// Circular buffer: overwrite oldest entry
|
|
443
|
+
this.history[this.historyIndex] = entry;
|
|
444
|
+
this.historyIndex = (this.historyIndex + 1) % maxEntries;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// Optimized output writing with batching
|
|
448
|
+
writeToOutputs(entry, level) {
|
|
449
|
+
if (this.outputs.size === 0)
|
|
450
|
+
return;
|
|
451
|
+
let successCount = 0;
|
|
452
|
+
const errors = [];
|
|
453
|
+
for (const output of this.outputs.values()) {
|
|
454
|
+
if (!output.level || MoroLogger.LEVELS[level] >= MoroLogger.LEVELS[output.level]) {
|
|
455
|
+
try {
|
|
456
|
+
output.write(entry);
|
|
457
|
+
successCount++;
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
errors.push({ outputName: output.name, error });
|
|
461
|
+
this.handleOutputError(output.name, error);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// If all outputs fail, use emergency console
|
|
466
|
+
if (successCount === 0 && this.outputs.size > 0) {
|
|
467
|
+
this.emergencyConsoleWrite(entry);
|
|
468
|
+
}
|
|
469
|
+
// Log output errors (but avoid infinite loops)
|
|
470
|
+
if (errors.length > 0 && level !== 'error') {
|
|
471
|
+
this.error(`Logger output errors: ${errors.length} failed`, 'MoroLogger', {
|
|
472
|
+
errors: errors.map(e => e.outputName),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
214
476
|
writeToConsole(entry) {
|
|
215
477
|
const format = this.options.format || 'pretty';
|
|
216
478
|
if (format === 'json') {
|
|
217
|
-
|
|
479
|
+
this.output(`${this.safeStringify(entry)}\n`, entry.level);
|
|
218
480
|
return;
|
|
219
481
|
}
|
|
220
482
|
if (format === 'compact') {
|
|
221
483
|
const level = entry.level.toUpperCase().padEnd(5);
|
|
222
484
|
const context = entry.context ? `[${entry.context}] ` : '';
|
|
223
|
-
|
|
485
|
+
this.output(`${level} ${context}${entry.message}\n`, entry.level);
|
|
224
486
|
return;
|
|
225
487
|
}
|
|
226
488
|
// Pretty format (default)
|
|
@@ -228,24 +490,47 @@ class MoroLogger {
|
|
|
228
490
|
}
|
|
229
491
|
writePrettyLog(entry) {
|
|
230
492
|
const colors = this.options.enableColors !== false;
|
|
231
|
-
const
|
|
232
|
-
|
|
493
|
+
const levelReset = colors ? MoroLogger.RESET : '';
|
|
494
|
+
MoroLogger.resetStringBuilder();
|
|
495
|
+
// Timestamp with caching optimization
|
|
233
496
|
if (this.options.enableTimestamp !== false) {
|
|
234
|
-
const timestamp = entry.timestamp
|
|
235
|
-
|
|
497
|
+
const timestamp = this.getCachedTimestamp(entry.timestamp);
|
|
498
|
+
if (colors) {
|
|
499
|
+
MoroLogger.appendToBuilder(MoroLogger.COLORS.timestamp);
|
|
500
|
+
MoroLogger.appendToBuilder(timestamp);
|
|
501
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
MoroLogger.appendToBuilder(timestamp);
|
|
505
|
+
}
|
|
506
|
+
MoroLogger.appendToBuilder(' ');
|
|
507
|
+
}
|
|
508
|
+
// Level with pre-allocated strings
|
|
509
|
+
const levelStr = MoroLogger.LEVEL_STRINGS[entry.level];
|
|
510
|
+
if (colors) {
|
|
511
|
+
MoroLogger.appendToBuilder(MoroLogger.COLORS[entry.level]);
|
|
512
|
+
MoroLogger.appendToBuilder(MoroLogger.BOLD);
|
|
513
|
+
MoroLogger.appendToBuilder(levelStr);
|
|
514
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
MoroLogger.appendToBuilder(levelStr);
|
|
236
518
|
}
|
|
237
|
-
// Level with color (remove icons)
|
|
238
|
-
const levelColor = colors ? MoroLogger.COLORS[entry.level] : '';
|
|
239
|
-
const levelReset = colors ? MoroLogger.RESET : '';
|
|
240
|
-
const levelText = entry.level.toUpperCase();
|
|
241
|
-
parts.push(`${levelColor}${MoroLogger.BOLD}${levelText}${levelReset}`);
|
|
242
519
|
// Context
|
|
243
520
|
if (entry.context && this.options.enableContext !== false) {
|
|
244
|
-
|
|
245
|
-
|
|
521
|
+
MoroLogger.appendToBuilder(' ');
|
|
522
|
+
if (colors) {
|
|
523
|
+
MoroLogger.appendToBuilder(MoroLogger.COLORS.context);
|
|
524
|
+
MoroLogger.appendToBuilder(`[${entry.context}]`);
|
|
525
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
MoroLogger.appendToBuilder(`[${entry.context}]`);
|
|
529
|
+
}
|
|
246
530
|
}
|
|
247
531
|
// Message
|
|
248
|
-
|
|
532
|
+
MoroLogger.appendToBuilder(' ');
|
|
533
|
+
MoroLogger.appendToBuilder(entry.message);
|
|
249
534
|
// Performance info
|
|
250
535
|
if (entry.performance && this.options.enablePerformance !== false) {
|
|
251
536
|
const perfColor = colors ? MoroLogger.COLORS.performance : '';
|
|
@@ -257,26 +542,248 @@ class MoroLogger {
|
|
|
257
542
|
perfParts.push(`${Math.round(entry.performance.memory)}MB`);
|
|
258
543
|
}
|
|
259
544
|
if (perfParts.length > 0) {
|
|
260
|
-
|
|
545
|
+
MoroLogger.appendToBuilder(' ');
|
|
546
|
+
if (colors) {
|
|
547
|
+
MoroLogger.appendToBuilder(perfColor);
|
|
548
|
+
MoroLogger.appendToBuilder(`(${perfParts.join(', ')})`);
|
|
549
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
MoroLogger.appendToBuilder(`(${perfParts.join(', ')})`);
|
|
553
|
+
}
|
|
261
554
|
}
|
|
262
555
|
}
|
|
263
|
-
// Metadata
|
|
556
|
+
// Metadata with optimized JSON stringify
|
|
264
557
|
if (entry.metadata &&
|
|
265
558
|
Object.keys(entry.metadata).length > 0 &&
|
|
266
559
|
this.options.enableMetadata !== false) {
|
|
267
560
|
const metaColor = colors ? MoroLogger.COLORS.metadata : '';
|
|
268
|
-
const cleanMetadata =
|
|
269
|
-
delete cleanMetadata.stack; // Handle stack separately
|
|
561
|
+
const cleanMetadata = this.cleanMetadata(entry.metadata);
|
|
270
562
|
if (Object.keys(cleanMetadata).length > 0) {
|
|
271
|
-
|
|
563
|
+
MoroLogger.appendToBuilder(' ');
|
|
564
|
+
if (colors) {
|
|
565
|
+
MoroLogger.appendToBuilder(metaColor);
|
|
566
|
+
MoroLogger.appendToBuilder(this.safeStringify(cleanMetadata));
|
|
567
|
+
MoroLogger.appendToBuilder(levelReset);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
MoroLogger.appendToBuilder(this.safeStringify(cleanMetadata));
|
|
571
|
+
}
|
|
272
572
|
}
|
|
273
573
|
}
|
|
274
|
-
// Output main log line
|
|
275
|
-
|
|
574
|
+
// Output main log line with high-performance method
|
|
575
|
+
const finalMessage = MoroLogger.buildString();
|
|
576
|
+
this.output(`${finalMessage}\n`, entry.level);
|
|
276
577
|
// Stack trace for errors
|
|
277
578
|
if (entry.metadata?.stack && (entry.level === 'error' || entry.level === 'fatal')) {
|
|
278
579
|
const stackColor = colors ? MoroLogger.COLORS.error : '';
|
|
279
|
-
|
|
580
|
+
this.output(`${stackColor}${entry.metadata.stack}${levelReset}\n`, entry.level);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Optimized metadata cleaning to avoid unnecessary object operations
|
|
584
|
+
cleanMetadata(metadata) {
|
|
585
|
+
const clean = {};
|
|
586
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
587
|
+
if (key !== 'stack') {
|
|
588
|
+
clean[key] = value;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return clean;
|
|
592
|
+
}
|
|
593
|
+
// High-performance output with buffering
|
|
594
|
+
output(message, level = 'info') {
|
|
595
|
+
// Prevent memory exhaustion
|
|
596
|
+
if (this.outputBuffer.length >= this.bufferOverflowThreshold &&
|
|
597
|
+
!this.emergencyFlushInProgress) {
|
|
598
|
+
this.emergencyFlushInProgress = true;
|
|
599
|
+
this.forceFlushBuffer();
|
|
600
|
+
this.emergencyFlushInProgress = false;
|
|
601
|
+
}
|
|
602
|
+
this.outputBuffer.push(message);
|
|
603
|
+
this.bufferSize++;
|
|
604
|
+
// Immediate flush for critical levels or full buffer
|
|
605
|
+
if (level === 'fatal' || level === 'error' || this.bufferSize >= this.maxBufferSize) {
|
|
606
|
+
this.flushBuffer();
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
this.scheduleFlush();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
scheduleFlush() {
|
|
613
|
+
if (this.flushTimeout) {
|
|
614
|
+
return; // Already scheduled
|
|
615
|
+
}
|
|
616
|
+
this.flushTimeout = setTimeout(() => {
|
|
617
|
+
this.flushBuffer();
|
|
618
|
+
}, this.flushInterval);
|
|
619
|
+
}
|
|
620
|
+
flushBuffer() {
|
|
621
|
+
if (this.outputBuffer.length === 0) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
// Group messages by stream type
|
|
625
|
+
const stdoutMessages = [];
|
|
626
|
+
const stderrMessages = [];
|
|
627
|
+
for (const message of this.outputBuffer) {
|
|
628
|
+
// Determine stream based on message content or level
|
|
629
|
+
if (message.includes('ERROR') || message.includes('FATAL')) {
|
|
630
|
+
stderrMessages.push(message);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
stdoutMessages.push(message);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// Write to appropriate streams with error handling
|
|
637
|
+
try {
|
|
638
|
+
if (stdoutMessages.length > 0 && process.stdout.writable) {
|
|
639
|
+
process.stdout.write(stdoutMessages.join(''));
|
|
640
|
+
}
|
|
641
|
+
if (stderrMessages.length > 0 && process.stderr.writable) {
|
|
642
|
+
process.stderr.write(stderrMessages.join(''));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
// Fallback to console if streams fail
|
|
647
|
+
try {
|
|
648
|
+
// eslint-disable-next-line no-console
|
|
649
|
+
console.log(this.outputBuffer.join(''));
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
// If even console.log fails, just ignore
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// Clear buffer
|
|
656
|
+
this.outputBuffer.length = 0;
|
|
657
|
+
this.bufferSize = 0;
|
|
658
|
+
// Clear timeout
|
|
659
|
+
if (this.flushTimeout) {
|
|
660
|
+
clearTimeout(this.flushTimeout);
|
|
661
|
+
this.flushTimeout = null;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// Emergency flush for buffer overflow protection
|
|
665
|
+
forceFlushBuffer() {
|
|
666
|
+
if (this.outputBuffer.length === 0)
|
|
667
|
+
return;
|
|
668
|
+
try {
|
|
669
|
+
const message = this.outputBuffer.join('');
|
|
670
|
+
process.stdout.write(message);
|
|
671
|
+
}
|
|
672
|
+
catch (error) {
|
|
673
|
+
// Emergency fallback - write individual messages
|
|
674
|
+
for (const msg of this.outputBuffer) {
|
|
675
|
+
try {
|
|
676
|
+
process.stdout.write(msg);
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
// If even this fails, give up on this batch
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
finally {
|
|
685
|
+
this.outputBuffer.length = 0;
|
|
686
|
+
this.bufferSize = 0;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// Safe stringify with circular reference detection
|
|
690
|
+
safeStringify(obj, maxDepth = 3) {
|
|
691
|
+
const seen = new WeakSet();
|
|
692
|
+
const stringify = (value, depth) => {
|
|
693
|
+
if (depth > maxDepth)
|
|
694
|
+
return '[Max Depth Reached]';
|
|
695
|
+
if (value === null || typeof value !== 'object')
|
|
696
|
+
return value;
|
|
697
|
+
if (seen.has(value))
|
|
698
|
+
return '[Circular Reference]';
|
|
699
|
+
seen.add(value);
|
|
700
|
+
if (Array.isArray(value)) {
|
|
701
|
+
return value.map(item => stringify(item, depth + 1));
|
|
702
|
+
}
|
|
703
|
+
const result = {};
|
|
704
|
+
for (const [key, val] of Object.entries(value)) {
|
|
705
|
+
if (typeof val !== 'function') {
|
|
706
|
+
// Skip functions
|
|
707
|
+
result[key] = stringify(val, depth + 1);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
};
|
|
712
|
+
try {
|
|
713
|
+
return JSON.stringify(stringify(obj, 0));
|
|
714
|
+
}
|
|
715
|
+
catch (error) {
|
|
716
|
+
return '[Stringify Error]';
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// Configuration validation
|
|
720
|
+
validateOptions(options) {
|
|
721
|
+
const validated = { ...options };
|
|
722
|
+
// Validate log level
|
|
723
|
+
const validLevels = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
724
|
+
if (validated.level && !validLevels.includes(validated.level)) {
|
|
725
|
+
console.warn(`[MoroLogger] Invalid log level: ${validated.level}, defaulting to 'info'`);
|
|
726
|
+
validated.level = 'info';
|
|
727
|
+
}
|
|
728
|
+
// Validate max entries
|
|
729
|
+
if (validated.maxEntries !== undefined) {
|
|
730
|
+
if (validated.maxEntries < 1 || validated.maxEntries > 100000) {
|
|
731
|
+
console.warn(`[MoroLogger] Invalid maxEntries: ${validated.maxEntries}, defaulting to 1000`);
|
|
732
|
+
validated.maxEntries = 1000;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
// Validate buffer size
|
|
736
|
+
if (validated.maxBufferSize !== undefined) {
|
|
737
|
+
if (validated.maxBufferSize < 10 || validated.maxBufferSize > 10000) {
|
|
738
|
+
console.warn(`[MoroLogger] Invalid maxBufferSize: ${validated.maxBufferSize}, defaulting to 1000`);
|
|
739
|
+
validated.maxBufferSize = 1000;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return validated;
|
|
743
|
+
}
|
|
744
|
+
// Error handling methods
|
|
745
|
+
handleOutputError(outputName, error) {
|
|
746
|
+
// Could implement output retry logic, circuit breaker, etc.
|
|
747
|
+
// For now, just track the error
|
|
748
|
+
if (!this.metrics.outputErrors) {
|
|
749
|
+
this.metrics.outputErrors = {};
|
|
750
|
+
}
|
|
751
|
+
this.metrics.outputErrors[outputName] = (this.metrics.outputErrors[outputName] || 0) + 1;
|
|
752
|
+
}
|
|
753
|
+
emergencyConsoleWrite(entry) {
|
|
754
|
+
const message = `${entry.timestamp.toISOString()} ${entry.level.toUpperCase()} ${entry.message}`;
|
|
755
|
+
try {
|
|
756
|
+
if (entry.level === 'error' || entry.level === 'fatal') {
|
|
757
|
+
process.stderr.write(`[EMERGENCY] ${message}\n`);
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
process.stdout.write(`[EMERGENCY] ${message}\n`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch {
|
|
764
|
+
// If even emergency write fails, there's nothing more we can do
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// Force flush streams (useful for shutdown)
|
|
768
|
+
flush() {
|
|
769
|
+
// Clear any pending flush timeout
|
|
770
|
+
if (this.flushTimeout) {
|
|
771
|
+
clearTimeout(this.flushTimeout);
|
|
772
|
+
this.flushTimeout = null;
|
|
773
|
+
}
|
|
774
|
+
// Flush any remaining buffer
|
|
775
|
+
this.flushBuffer();
|
|
776
|
+
try {
|
|
777
|
+
// Force flush streams without ending them
|
|
778
|
+
if (process.stdout.writable) {
|
|
779
|
+
process.stdout.write(''); // Force flush without ending
|
|
780
|
+
}
|
|
781
|
+
if (process.stderr.writable) {
|
|
782
|
+
process.stderr.write(''); // Force flush without ending
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
catch {
|
|
786
|
+
// Ignore flush errors
|
|
280
787
|
}
|
|
281
788
|
}
|
|
282
789
|
}
|
|
@@ -325,4 +832,16 @@ const createFrameworkLogger = (context) => {
|
|
|
325
832
|
return exports.logger.child('Moro', { framework: 'moro', context });
|
|
326
833
|
};
|
|
327
834
|
exports.createFrameworkLogger = createFrameworkLogger;
|
|
835
|
+
// Graceful shutdown handler to flush any pending logs
|
|
836
|
+
process.on('SIGINT', () => {
|
|
837
|
+
exports.logger.flush();
|
|
838
|
+
process.exit(0);
|
|
839
|
+
});
|
|
840
|
+
process.on('SIGTERM', () => {
|
|
841
|
+
exports.logger.flush();
|
|
842
|
+
process.exit(0);
|
|
843
|
+
});
|
|
844
|
+
process.on('beforeExit', () => {
|
|
845
|
+
exports.logger.flush();
|
|
846
|
+
});
|
|
328
847
|
//# sourceMappingURL=logger.js.map
|