@morojs/moro 1.5.3 → 1.5.4

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 (36) 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 +44 -0
  8. package/dist/core/logger/logger.js +550 -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/types/logger.d.ts +3 -0
  25. package/package.json +1 -1
  26. package/src/core/auth/morojs-adapter.ts +25 -12
  27. package/src/core/database/README.md +26 -16
  28. package/src/core/http/http-server.ts +15 -12
  29. package/src/core/logger/filters.ts +12 -4
  30. package/src/core/logger/logger.ts +616 -63
  31. package/src/core/middleware/built-in/request-logger.ts +6 -2
  32. package/src/core/modules/auto-discovery.ts +13 -5
  33. package/src/core/modules/modules.ts +9 -5
  34. package/src/core/networking/adapters/ws-adapter.ts +3 -1
  35. package/src/core/networking/service-discovery.ts +23 -9
  36. 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,72 +276,184 @@ 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
+ const logStr = level.toUpperCase() + ' ' + message + '\n';
289
+ if (level === 'error' || level === 'fatal') {
290
+ process.stderr.write(logStr);
291
+ }
292
+ else {
293
+ process.stdout.write(logStr);
294
+ }
295
+ return;
296
+ }
297
+ // Minimal path for logs with context but no metadata
298
+ if (!metadata && !this.contextMetadata) {
299
+ const contextStr = context ? '[' + context + '] ' : '';
300
+ const logStr = level.toUpperCase() + ' ' + contextStr + message + '\n';
301
+ if (level === 'error' || level === 'fatal') {
302
+ process.stderr.write(logStr);
303
+ }
304
+ else {
305
+ process.stdout.write(logStr);
306
+ }
307
+ return;
308
+ }
309
+ // Path for complex logs
310
+ if (metadata && Object.keys(metadata).length > 0) {
311
+ this.complexLog(level, message, context, metadata);
312
+ return;
313
+ }
314
+ // Full logging path for complex logs
315
+ this.fullLog(level, message, context, metadata);
316
+ }
317
+ // Full logging with all features
318
+ fullLog(level, message, context, metadata) {
319
+ // Use object pooling for LogEntry (Pino's technique)
320
+ const entry = MoroLogger.getPooledEntry();
321
+ const now = Date.now();
322
+ entry.timestamp = new Date(now);
323
+ entry.level = level;
324
+ entry.message = message;
325
+ entry.context = this.contextPrefix
326
+ ? context
327
+ ? `${this.contextPrefix}:${context}`
328
+ : this.contextPrefix
329
+ : context;
330
+ entry.metadata = this.createMetadata(metadata);
331
+ entry.performance = this.options.enablePerformance ? this.getPerformanceData(now) : undefined;
332
+ // Apply filters with early return optimization
333
+ if (this.filters.size > 0) {
334
+ for (const filter of this.filters.values()) {
335
+ if (!filter.filter(entry)) {
336
+ MoroLogger.returnPooledEntry(entry);
337
+ return;
178
338
  }
179
- : undefined,
180
- };
181
- // Apply filters
182
- for (const filter of this.filters.values()) {
183
- if (!filter.filter(entry)) {
184
- return;
185
339
  }
186
340
  }
187
341
  // Update metrics
188
342
  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();
343
+ // Store in history with circular buffer optimization
344
+ this.addToHistory(entry);
345
+ // Write to outputs with batched processing
346
+ this.writeToOutputs(entry, level);
347
+ // Return entry to pool
348
+ MoroLogger.returnPooledEntry(entry);
349
+ }
350
+ // Absolute minimal logging - pure speed, no overhead
351
+ complexLog(level, message, context, metadata) {
352
+ // Build string directly - no array, no try-catch, no metrics
353
+ let logStr = this.getFastCachedTimestamp() + ' ' + level.toUpperCase().padEnd(5);
354
+ if (context) {
355
+ logStr += '[' + context + '] ';
356
+ }
357
+ else if (this.contextPrefix) {
358
+ logStr += '[' + this.contextPrefix + '] ';
359
+ }
360
+ logStr += message;
361
+ if (metadata && Object.keys(metadata).length > 0) {
362
+ logStr += ' ' + this.safeStringify(metadata);
363
+ }
364
+ if (this.contextMetadata && Object.keys(this.contextMetadata).length > 0) {
365
+ logStr += ' ' + this.safeStringify(this.contextMetadata);
366
+ }
367
+ logStr += '\n';
368
+ // Direct write - no error handling, no buffering
369
+ if (level === 'error' || level === 'fatal') {
370
+ process.stderr.write(logStr);
371
+ }
372
+ else {
373
+ process.stdout.write(logStr);
374
+ }
375
+ }
376
+ updateMetrics(entry) {
377
+ this.metrics.totalLogs++;
378
+ this.metrics.logsByLevel[entry.level]++;
379
+ if (entry.context) {
380
+ this.metrics.logsByContext[entry.context] =
381
+ (this.metrics.logsByContext[entry.context] || 0) + 1;
382
+ }
383
+ }
384
+ // Optimized metadata creation to avoid unnecessary object spreading
385
+ createMetadata(metadata) {
386
+ if (!metadata && !this.contextMetadata) {
387
+ return {};
193
388
  }
194
- // Write to outputs
389
+ if (!metadata) {
390
+ return { ...this.contextMetadata };
391
+ }
392
+ if (!this.contextMetadata) {
393
+ return { ...metadata };
394
+ }
395
+ return { ...this.contextMetadata, ...metadata };
396
+ }
397
+ // Optimized performance data with caching
398
+ getPerformanceData(now) {
399
+ if (now - this.lastMemoryCheck > this.memoryCheckInterval) {
400
+ this.lastMemoryCheck = now;
401
+ this.metrics.memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
402
+ }
403
+ return { memory: this.metrics.memoryUsage };
404
+ }
405
+ // Circular buffer implementation for history (O(1) instead of O(n))
406
+ addToHistory(entry) {
407
+ const maxEntries = this.options.maxEntries || 1000;
408
+ if (this.historySize < maxEntries) {
409
+ this.history[this.historySize] = entry;
410
+ this.historySize++;
411
+ }
412
+ else {
413
+ // Circular buffer: overwrite oldest entry
414
+ this.history[this.historyIndex] = entry;
415
+ this.historyIndex = (this.historyIndex + 1) % maxEntries;
416
+ }
417
+ }
418
+ // Optimized output writing with batching
419
+ writeToOutputs(entry, level) {
420
+ if (this.outputs.size === 0)
421
+ return;
422
+ let successCount = 0;
423
+ const errors = [];
195
424
  for (const output of this.outputs.values()) {
196
425
  if (!output.level || MoroLogger.LEVELS[level] >= MoroLogger.LEVELS[output.level]) {
197
426
  try {
198
427
  output.write(entry);
428
+ successCount++;
199
429
  }
200
430
  catch (error) {
201
- console.error('Logger output error:', error);
431
+ errors.push({ outputName: output.name, error });
432
+ this.handleOutputError(output.name, error);
202
433
  }
203
434
  }
204
435
  }
205
- }
206
- updateMetrics(entry) {
207
- this.metrics.totalLogs++;
208
- this.metrics.logsByLevel[entry.level]++;
209
- if (entry.context) {
210
- this.metrics.logsByContext[entry.context] =
211
- (this.metrics.logsByContext[entry.context] || 0) + 1;
436
+ // If all outputs fail, use emergency console
437
+ if (successCount === 0 && this.outputs.size > 0) {
438
+ this.emergencyConsoleWrite(entry);
439
+ }
440
+ // Log output errors (but avoid infinite loops)
441
+ if (errors.length > 0 && level !== 'error') {
442
+ this.error(`Logger output errors: ${errors.length} failed`, 'MoroLogger', {
443
+ errors: errors.map(e => e.outputName),
444
+ });
212
445
  }
213
446
  }
214
447
  writeToConsole(entry) {
215
448
  const format = this.options.format || 'pretty';
216
449
  if (format === 'json') {
217
- console.log(JSON.stringify(entry));
450
+ this.output(`${this.safeStringify(entry)}\n`, entry.level);
218
451
  return;
219
452
  }
220
453
  if (format === 'compact') {
221
454
  const level = entry.level.toUpperCase().padEnd(5);
222
455
  const context = entry.context ? `[${entry.context}] ` : '';
223
- console.log(`${level} ${context}${entry.message}`);
456
+ this.output(`${level} ${context}${entry.message}\n`, entry.level);
224
457
  return;
225
458
  }
226
459
  // Pretty format (default)
@@ -228,24 +461,47 @@ class MoroLogger {
228
461
  }
229
462
  writePrettyLog(entry) {
230
463
  const colors = this.options.enableColors !== false;
231
- const parts = [];
232
- // Timestamp
464
+ const levelReset = colors ? MoroLogger.RESET : '';
465
+ MoroLogger.resetStringBuilder();
466
+ // Timestamp with caching optimization
233
467
  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);
468
+ const timestamp = this.getCachedTimestamp(entry.timestamp);
469
+ if (colors) {
470
+ MoroLogger.appendToBuilder(MoroLogger.COLORS.timestamp);
471
+ MoroLogger.appendToBuilder(timestamp);
472
+ MoroLogger.appendToBuilder(levelReset);
473
+ }
474
+ else {
475
+ MoroLogger.appendToBuilder(timestamp);
476
+ }
477
+ MoroLogger.appendToBuilder(' ');
478
+ }
479
+ // Level with pre-allocated strings
480
+ const levelStr = MoroLogger.LEVEL_STRINGS[entry.level];
481
+ if (colors) {
482
+ MoroLogger.appendToBuilder(MoroLogger.COLORS[entry.level]);
483
+ MoroLogger.appendToBuilder(MoroLogger.BOLD);
484
+ MoroLogger.appendToBuilder(levelStr);
485
+ MoroLogger.appendToBuilder(levelReset);
486
+ }
487
+ else {
488
+ MoroLogger.appendToBuilder(levelStr);
236
489
  }
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
490
  // Context
243
491
  if (entry.context && this.options.enableContext !== false) {
244
- const contextColor = colors ? MoroLogger.COLORS.context : '';
245
- parts.push(`${contextColor}[${entry.context}]${levelReset}`);
492
+ MoroLogger.appendToBuilder(' ');
493
+ if (colors) {
494
+ MoroLogger.appendToBuilder(MoroLogger.COLORS.context);
495
+ MoroLogger.appendToBuilder(`[${entry.context}]`);
496
+ MoroLogger.appendToBuilder(levelReset);
497
+ }
498
+ else {
499
+ MoroLogger.appendToBuilder(`[${entry.context}]`);
500
+ }
246
501
  }
247
502
  // Message
248
- parts.push(entry.message);
503
+ MoroLogger.appendToBuilder(' ');
504
+ MoroLogger.appendToBuilder(entry.message);
249
505
  // Performance info
250
506
  if (entry.performance && this.options.enablePerformance !== false) {
251
507
  const perfColor = colors ? MoroLogger.COLORS.performance : '';
@@ -257,26 +513,248 @@ class MoroLogger {
257
513
  perfParts.push(`${Math.round(entry.performance.memory)}MB`);
258
514
  }
259
515
  if (perfParts.length > 0) {
260
- parts.push(`${perfColor}(${perfParts.join(', ')})${levelReset}`);
516
+ MoroLogger.appendToBuilder(' ');
517
+ if (colors) {
518
+ MoroLogger.appendToBuilder(perfColor);
519
+ MoroLogger.appendToBuilder(`(${perfParts.join(', ')})`);
520
+ MoroLogger.appendToBuilder(levelReset);
521
+ }
522
+ else {
523
+ MoroLogger.appendToBuilder(`(${perfParts.join(', ')})`);
524
+ }
261
525
  }
262
526
  }
263
- // Metadata
527
+ // Metadata with optimized JSON stringify
264
528
  if (entry.metadata &&
265
529
  Object.keys(entry.metadata).length > 0 &&
266
530
  this.options.enableMetadata !== false) {
267
531
  const metaColor = colors ? MoroLogger.COLORS.metadata : '';
268
- const cleanMetadata = { ...entry.metadata };
269
- delete cleanMetadata.stack; // Handle stack separately
532
+ const cleanMetadata = this.cleanMetadata(entry.metadata);
270
533
  if (Object.keys(cleanMetadata).length > 0) {
271
- parts.push(`${metaColor}${JSON.stringify(cleanMetadata)}${levelReset}`);
534
+ MoroLogger.appendToBuilder(' ');
535
+ if (colors) {
536
+ MoroLogger.appendToBuilder(metaColor);
537
+ MoroLogger.appendToBuilder(this.safeStringify(cleanMetadata));
538
+ MoroLogger.appendToBuilder(levelReset);
539
+ }
540
+ else {
541
+ MoroLogger.appendToBuilder(this.safeStringify(cleanMetadata));
542
+ }
272
543
  }
273
544
  }
274
- // Output main log line
275
- console.log(parts.join(' '));
545
+ // Output main log line with high-performance method
546
+ const finalMessage = MoroLogger.buildString();
547
+ this.output(`${finalMessage}\n`, entry.level);
276
548
  // Stack trace for errors
277
549
  if (entry.metadata?.stack && (entry.level === 'error' || entry.level === 'fatal')) {
278
550
  const stackColor = colors ? MoroLogger.COLORS.error : '';
279
- console.log(`${stackColor}${entry.metadata.stack}${levelReset}`);
551
+ this.output(`${stackColor}${entry.metadata.stack}${levelReset}\n`, entry.level);
552
+ }
553
+ }
554
+ // Optimized metadata cleaning to avoid unnecessary object operations
555
+ cleanMetadata(metadata) {
556
+ const clean = {};
557
+ for (const [key, value] of Object.entries(metadata)) {
558
+ if (key !== 'stack') {
559
+ clean[key] = value;
560
+ }
561
+ }
562
+ return clean;
563
+ }
564
+ // High-performance output with buffering
565
+ output(message, level = 'info') {
566
+ // Prevent memory exhaustion
567
+ if (this.outputBuffer.length >= this.bufferOverflowThreshold &&
568
+ !this.emergencyFlushInProgress) {
569
+ this.emergencyFlushInProgress = true;
570
+ this.forceFlushBuffer();
571
+ this.emergencyFlushInProgress = false;
572
+ }
573
+ this.outputBuffer.push(message);
574
+ this.bufferSize++;
575
+ // Immediate flush for critical levels or full buffer
576
+ if (level === 'fatal' || level === 'error' || this.bufferSize >= this.maxBufferSize) {
577
+ this.flushBuffer();
578
+ }
579
+ else {
580
+ this.scheduleFlush();
581
+ }
582
+ }
583
+ scheduleFlush() {
584
+ if (this.flushTimeout) {
585
+ return; // Already scheduled
586
+ }
587
+ this.flushTimeout = setTimeout(() => {
588
+ this.flushBuffer();
589
+ }, this.flushInterval);
590
+ }
591
+ flushBuffer() {
592
+ if (this.outputBuffer.length === 0) {
593
+ return;
594
+ }
595
+ // Group messages by stream type
596
+ const stdoutMessages = [];
597
+ const stderrMessages = [];
598
+ for (const message of this.outputBuffer) {
599
+ // Determine stream based on message content or level
600
+ if (message.includes('ERROR') || message.includes('FATAL')) {
601
+ stderrMessages.push(message);
602
+ }
603
+ else {
604
+ stdoutMessages.push(message);
605
+ }
606
+ }
607
+ // Write to appropriate streams with error handling
608
+ try {
609
+ if (stdoutMessages.length > 0 && process.stdout.writable) {
610
+ process.stdout.write(stdoutMessages.join(''));
611
+ }
612
+ if (stderrMessages.length > 0 && process.stderr.writable) {
613
+ process.stderr.write(stderrMessages.join(''));
614
+ }
615
+ }
616
+ catch {
617
+ // Fallback to console if streams fail
618
+ try {
619
+ // eslint-disable-next-line no-console
620
+ console.log(this.outputBuffer.join(''));
621
+ }
622
+ catch {
623
+ // If even console.log fails, just ignore
624
+ }
625
+ }
626
+ // Clear buffer
627
+ this.outputBuffer.length = 0;
628
+ this.bufferSize = 0;
629
+ // Clear timeout
630
+ if (this.flushTimeout) {
631
+ clearTimeout(this.flushTimeout);
632
+ this.flushTimeout = null;
633
+ }
634
+ }
635
+ // Emergency flush for buffer overflow protection
636
+ forceFlushBuffer() {
637
+ if (this.outputBuffer.length === 0)
638
+ return;
639
+ try {
640
+ const message = this.outputBuffer.join('');
641
+ process.stdout.write(message);
642
+ }
643
+ catch (error) {
644
+ // Emergency fallback - write individual messages
645
+ for (const msg of this.outputBuffer) {
646
+ try {
647
+ process.stdout.write(msg);
648
+ }
649
+ catch {
650
+ // If even this fails, give up on this batch
651
+ break;
652
+ }
653
+ }
654
+ }
655
+ finally {
656
+ this.outputBuffer.length = 0;
657
+ this.bufferSize = 0;
658
+ }
659
+ }
660
+ // Safe stringify with circular reference detection
661
+ safeStringify(obj, maxDepth = 3) {
662
+ const seen = new WeakSet();
663
+ const stringify = (value, depth) => {
664
+ if (depth > maxDepth)
665
+ return '[Max Depth Reached]';
666
+ if (value === null || typeof value !== 'object')
667
+ return value;
668
+ if (seen.has(value))
669
+ return '[Circular Reference]';
670
+ seen.add(value);
671
+ if (Array.isArray(value)) {
672
+ return value.map(item => stringify(item, depth + 1));
673
+ }
674
+ const result = {};
675
+ for (const [key, val] of Object.entries(value)) {
676
+ if (typeof val !== 'function') {
677
+ // Skip functions
678
+ result[key] = stringify(val, depth + 1);
679
+ }
680
+ }
681
+ return result;
682
+ };
683
+ try {
684
+ return JSON.stringify(stringify(obj, 0));
685
+ }
686
+ catch (error) {
687
+ return '[Stringify Error]';
688
+ }
689
+ }
690
+ // Configuration validation
691
+ validateOptions(options) {
692
+ const validated = { ...options };
693
+ // Validate log level
694
+ const validLevels = ['debug', 'info', 'warn', 'error', 'fatal'];
695
+ if (validated.level && !validLevels.includes(validated.level)) {
696
+ console.warn(`[MoroLogger] Invalid log level: ${validated.level}, defaulting to 'info'`);
697
+ validated.level = 'info';
698
+ }
699
+ // Validate max entries
700
+ if (validated.maxEntries !== undefined) {
701
+ if (validated.maxEntries < 1 || validated.maxEntries > 100000) {
702
+ console.warn(`[MoroLogger] Invalid maxEntries: ${validated.maxEntries}, defaulting to 1000`);
703
+ validated.maxEntries = 1000;
704
+ }
705
+ }
706
+ // Validate buffer size
707
+ if (validated.maxBufferSize !== undefined) {
708
+ if (validated.maxBufferSize < 10 || validated.maxBufferSize > 10000) {
709
+ console.warn(`[MoroLogger] Invalid maxBufferSize: ${validated.maxBufferSize}, defaulting to 1000`);
710
+ validated.maxBufferSize = 1000;
711
+ }
712
+ }
713
+ return validated;
714
+ }
715
+ // Error handling methods
716
+ handleOutputError(outputName, error) {
717
+ // Could implement output retry logic, circuit breaker, etc.
718
+ // For now, just track the error
719
+ if (!this.metrics.outputErrors) {
720
+ this.metrics.outputErrors = {};
721
+ }
722
+ this.metrics.outputErrors[outputName] = (this.metrics.outputErrors[outputName] || 0) + 1;
723
+ }
724
+ emergencyConsoleWrite(entry) {
725
+ const message = `${entry.timestamp.toISOString()} ${entry.level.toUpperCase()} ${entry.message}`;
726
+ try {
727
+ if (entry.level === 'error' || entry.level === 'fatal') {
728
+ process.stderr.write(`[EMERGENCY] ${message}\n`);
729
+ }
730
+ else {
731
+ process.stdout.write(`[EMERGENCY] ${message}\n`);
732
+ }
733
+ }
734
+ catch {
735
+ // If even emergency write fails, there's nothing more we can do
736
+ }
737
+ }
738
+ // Force flush streams (useful for shutdown)
739
+ flush() {
740
+ // Clear any pending flush timeout
741
+ if (this.flushTimeout) {
742
+ clearTimeout(this.flushTimeout);
743
+ this.flushTimeout = null;
744
+ }
745
+ // Flush any remaining buffer
746
+ this.flushBuffer();
747
+ try {
748
+ // Force flush streams without ending them
749
+ if (process.stdout.writable) {
750
+ process.stdout.write(''); // Force flush without ending
751
+ }
752
+ if (process.stderr.writable) {
753
+ process.stderr.write(''); // Force flush without ending
754
+ }
755
+ }
756
+ catch {
757
+ // Ignore flush errors
280
758
  }
281
759
  }
282
760
  }
@@ -325,4 +803,16 @@ const createFrameworkLogger = (context) => {
325
803
  return exports.logger.child('Moro', { framework: 'moro', context });
326
804
  };
327
805
  exports.createFrameworkLogger = createFrameworkLogger;
806
+ // Graceful shutdown handler to flush any pending logs
807
+ process.on('SIGINT', () => {
808
+ exports.logger.flush();
809
+ process.exit(0);
810
+ });
811
+ process.on('SIGTERM', () => {
812
+ exports.logger.flush();
813
+ process.exit(0);
814
+ });
815
+ process.on('beforeExit', () => {
816
+ exports.logger.flush();
817
+ });
328
818
  //# sourceMappingURL=logger.js.map