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