@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
@@ -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
- const entries = [...this.history];
132
- return count ? entries.slice(-count) : entries;
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
- // Check level threshold - use parent level if available (for child loggers)
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
- // Create log entry
165
- const entry = {
166
- timestamp: new Date(),
167
- level,
168
- message,
169
- context: this.contextPrefix
170
- ? context
171
- ? `${this.contextPrefix}:${context}`
172
- : this.contextPrefix
173
- : context,
174
- metadata: { ...this.contextMetadata, ...metadata },
175
- performance: this.options.enablePerformance
176
- ? {
177
- memory: process.memoryUsage().heapUsed / 1024 / 1024,
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.history.push(entry);
191
- if (this.history.length > (this.options.maxEntries || 1000)) {
192
- this.history.shift();
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
- // Write to outputs
195
- for (const output of this.outputs.values()) {
196
- if (!output.level || MoroLogger.LEVELS[level] >= MoroLogger.LEVELS[output.level]) {
197
- try {
198
- output.write(entry);
199
- }
200
- catch (error) {
201
- console.error('Logger output error:', error);
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
- console.log(JSON.stringify(entry));
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
- console.log(`${level} ${context}${entry.message}`);
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 parts = [];
232
- // Timestamp
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.toISOString().replace('T', ' ').slice(0, 19);
235
- parts.push(colors ? `${MoroLogger.COLORS.timestamp}${timestamp}${MoroLogger.RESET}` : timestamp);
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
- const contextColor = colors ? MoroLogger.COLORS.context : '';
245
- parts.push(`${contextColor}[${entry.context}]${levelReset}`);
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
- parts.push(entry.message);
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
- parts.push(`${perfColor}(${perfParts.join(', ')})${levelReset}`);
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 = { ...entry.metadata };
269
- delete cleanMetadata.stack; // Handle stack separately
561
+ const cleanMetadata = this.cleanMetadata(entry.metadata);
270
562
  if (Object.keys(cleanMetadata).length > 0) {
271
- parts.push(`${metaColor}${JSON.stringify(cleanMetadata)}${levelReset}`);
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
- console.log(parts.join(' '));
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
- console.log(`${stackColor}${entry.metadata.stack}${levelReset}`);
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