@morojs/moro 1.5.17 → 1.6.1

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 (138) hide show
  1. package/README.md +48 -65
  2. package/dist/core/auth/morojs-adapter.js +12 -16
  3. package/dist/core/auth/morojs-adapter.js.map +1 -1
  4. package/dist/core/config/file-loader.d.ts +5 -0
  5. package/dist/core/config/file-loader.js +171 -0
  6. package/dist/core/config/file-loader.js.map +1 -1
  7. package/dist/core/config/index.d.ts +10 -39
  8. package/dist/core/config/index.js +29 -66
  9. package/dist/core/config/index.js.map +1 -1
  10. package/dist/core/config/loader.d.ts +7 -0
  11. package/dist/core/config/loader.js +269 -0
  12. package/dist/core/config/loader.js.map +1 -0
  13. package/dist/core/config/schema.js +31 -41
  14. package/dist/core/config/schema.js.map +1 -1
  15. package/dist/core/config/utils.d.ts +2 -9
  16. package/dist/core/config/utils.js +32 -19
  17. package/dist/core/config/utils.js.map +1 -1
  18. package/dist/core/config/validation.d.ts +17 -0
  19. package/dist/core/config/validation.js +131 -0
  20. package/dist/core/config/validation.js.map +1 -0
  21. package/dist/core/database/adapters/mongodb.d.ts +0 -10
  22. package/dist/core/database/adapters/mongodb.js +2 -23
  23. package/dist/core/database/adapters/mongodb.js.map +1 -1
  24. package/dist/core/database/adapters/mysql.d.ts +0 -11
  25. package/dist/core/database/adapters/mysql.js +0 -1
  26. package/dist/core/database/adapters/mysql.js.map +1 -1
  27. package/dist/core/database/adapters/postgresql.d.ts +1 -9
  28. package/dist/core/database/adapters/postgresql.js +1 -1
  29. package/dist/core/database/adapters/postgresql.js.map +1 -1
  30. package/dist/core/database/adapters/redis.d.ts +0 -9
  31. package/dist/core/database/adapters/redis.js +4 -14
  32. package/dist/core/database/adapters/redis.js.map +1 -1
  33. package/dist/core/framework.d.ts +7 -6
  34. package/dist/core/framework.js +16 -131
  35. package/dist/core/framework.js.map +1 -1
  36. package/dist/core/http/http-server.d.ts +0 -12
  37. package/dist/core/http/http-server.js +23 -151
  38. package/dist/core/http/http-server.js.map +1 -1
  39. package/dist/core/http/router.d.ts +0 -12
  40. package/dist/core/http/router.js +36 -114
  41. package/dist/core/http/router.js.map +1 -1
  42. package/dist/core/logger/filters.js +4 -12
  43. package/dist/core/logger/filters.js.map +1 -1
  44. package/dist/core/logger/index.d.ts +1 -1
  45. package/dist/core/logger/index.js +1 -2
  46. package/dist/core/logger/index.js.map +1 -1
  47. package/dist/core/logger/logger.d.ts +13 -29
  48. package/dist/core/logger/logger.js +203 -380
  49. package/dist/core/logger/logger.js.map +1 -1
  50. package/dist/core/logger/outputs.js +2 -0
  51. package/dist/core/logger/outputs.js.map +1 -1
  52. package/dist/core/middleware/built-in/auth.js +17 -88
  53. package/dist/core/middleware/built-in/auth.js.map +1 -1
  54. package/dist/core/middleware/built-in/cache.js +1 -3
  55. package/dist/core/middleware/built-in/cache.js.map +1 -1
  56. package/dist/core/middleware/built-in/index.d.ts +0 -1
  57. package/dist/core/middleware/built-in/index.js +1 -6
  58. package/dist/core/middleware/built-in/index.js.map +1 -1
  59. package/dist/core/middleware/built-in/request-logger.js +2 -3
  60. package/dist/core/middleware/built-in/request-logger.js.map +1 -1
  61. package/dist/core/middleware/built-in/sse.js +7 -9
  62. package/dist/core/middleware/built-in/sse.js.map +1 -1
  63. package/dist/core/modules/auto-discovery.d.ts +0 -17
  64. package/dist/core/modules/auto-discovery.js +12 -367
  65. package/dist/core/modules/auto-discovery.js.map +1 -1
  66. package/dist/core/modules/modules.js +2 -12
  67. package/dist/core/modules/modules.js.map +1 -1
  68. package/dist/core/networking/adapters/ws-adapter.d.ts +1 -1
  69. package/dist/core/networking/adapters/ws-adapter.js +2 -2
  70. package/dist/core/networking/adapters/ws-adapter.js.map +1 -1
  71. package/dist/core/networking/service-discovery.js +7 -7
  72. package/dist/core/networking/service-discovery.js.map +1 -1
  73. package/dist/core/routing/index.d.ts +0 -20
  74. package/dist/core/routing/index.js +13 -178
  75. package/dist/core/routing/index.js.map +1 -1
  76. package/dist/core/runtime/node-adapter.js +6 -12
  77. package/dist/core/runtime/node-adapter.js.map +1 -1
  78. package/dist/moro.d.ts +0 -48
  79. package/dist/moro.js +148 -456
  80. package/dist/moro.js.map +1 -1
  81. package/dist/types/config.d.ts +2 -58
  82. package/dist/types/core.d.ts +40 -34
  83. package/dist/types/http.d.ts +1 -16
  84. package/dist/types/logger.d.ts +0 -7
  85. package/dist/types/module.d.ts +0 -11
  86. package/package.json +2 -2
  87. package/src/core/auth/morojs-adapter.ts +13 -18
  88. package/src/core/config/file-loader.ts +233 -0
  89. package/src/core/config/index.ts +32 -77
  90. package/src/core/config/loader.ts +633 -0
  91. package/src/core/config/schema.ts +31 -41
  92. package/src/core/config/utils.ts +29 -22
  93. package/src/core/config/validation.ts +140 -0
  94. package/src/core/database/README.md +16 -26
  95. package/src/core/database/adapters/mongodb.ts +2 -30
  96. package/src/core/database/adapters/mysql.ts +0 -14
  97. package/src/core/database/adapters/postgresql.ts +2 -12
  98. package/src/core/database/adapters/redis.ts +4 -27
  99. package/src/core/framework.ts +23 -163
  100. package/src/core/http/http-server.ts +36 -176
  101. package/src/core/http/router.ts +38 -127
  102. package/src/core/logger/filters.ts +4 -12
  103. package/src/core/logger/index.ts +0 -1
  104. package/src/core/logger/logger.ts +216 -427
  105. package/src/core/logger/outputs.ts +2 -0
  106. package/src/core/middleware/built-in/auth.ts +17 -98
  107. package/src/core/middleware/built-in/cache.ts +1 -3
  108. package/src/core/middleware/built-in/index.ts +0 -8
  109. package/src/core/middleware/built-in/request-logger.ts +1 -3
  110. package/src/core/middleware/built-in/sse.ts +7 -9
  111. package/src/core/modules/auto-discovery.ts +13 -476
  112. package/src/core/modules/modules.ts +9 -20
  113. package/src/core/networking/adapters/ws-adapter.ts +5 -2
  114. package/src/core/networking/service-discovery.ts +7 -6
  115. package/src/core/routing/index.ts +14 -198
  116. package/src/core/runtime/node-adapter.ts +6 -12
  117. package/src/moro.ts +166 -554
  118. package/src/types/config.ts +2 -59
  119. package/src/types/core.ts +45 -47
  120. package/src/types/http.ts +1 -23
  121. package/src/types/logger.ts +0 -9
  122. package/src/types/module.ts +0 -12
  123. package/dist/core/config/config-manager.d.ts +0 -44
  124. package/dist/core/config/config-manager.js +0 -114
  125. package/dist/core/config/config-manager.js.map +0 -1
  126. package/dist/core/config/config-sources.d.ts +0 -21
  127. package/dist/core/config/config-sources.js +0 -502
  128. package/dist/core/config/config-sources.js.map +0 -1
  129. package/dist/core/config/config-validator.d.ts +0 -21
  130. package/dist/core/config/config-validator.js +0 -765
  131. package/dist/core/config/config-validator.js.map +0 -1
  132. package/dist/core/middleware/built-in/jwt-helpers.d.ts +0 -118
  133. package/dist/core/middleware/built-in/jwt-helpers.js +0 -221
  134. package/dist/core/middleware/built-in/jwt-helpers.js.map +0 -1
  135. package/src/core/config/config-manager.ts +0 -133
  136. package/src/core/config/config-sources.ts +0 -596
  137. package/src/core/config/config-validator.ts +0 -1078
  138. package/src/core/middleware/built-in/jwt-helpers.ts +0 -240
@@ -1,6 +1,6 @@
1
1
  // Moro Logger - Beautiful, Fast, Feature-Rich
2
2
  import { performance } from 'perf_hooks';
3
-
3
+ // import { format } from 'util'; // Not currently used
4
4
  import {
5
5
  LogLevel,
6
6
  LogEntry,
@@ -41,27 +41,13 @@ export class MoroLogger implements Logger {
41
41
  private lastTimestamp = 0;
42
42
  private timestampCacheInterval = 100; // 100ms for better precision
43
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
44
+ // Buffered output for micro-batching
54
45
  private outputBuffer: string[] = [];
55
46
  private bufferSize = 0;
56
- private maxBufferSize = 1000;
47
+ private maxBufferSize = 1024; // 1KB buffer
57
48
  private flushTimeout: NodeJS.Timeout | null = null;
58
49
  private flushInterval = 1; // 1ms micro-batching
59
50
 
60
- // Buffer overflow protection
61
- private bufferOverflowThreshold: number;
62
- private emergencyFlushInProgress = false;
63
- private isDestroyed = false;
64
-
65
51
  // High-performance output methods
66
52
 
67
53
  private static readonly LEVELS: Record<LogLevel, number> = {
@@ -72,6 +58,84 @@ export class MoroLogger implements Logger {
72
58
  fatal: 4,
73
59
  };
74
60
 
61
+ // Static pre-allocated strings for maximum performance
62
+ private static readonly LEVEL_STRINGS: Record<LogLevel, string> = {
63
+ debug: 'DEBUG',
64
+ info: 'INFO',
65
+ warn: 'WARN',
66
+ error: 'ERROR',
67
+ fatal: 'FATAL',
68
+ };
69
+
70
+ // Pre-allocated ANSI color codes
71
+ private static readonly ANSI_COLORS = {
72
+ reset: '\x1b[0m',
73
+ bold: '\x1b[1m',
74
+ dim: '\x1b[2m',
75
+ red: '\x1b[31m',
76
+ green: '\x1b[32m',
77
+ yellow: '\x1b[33m',
78
+ blue: '\x1b[34m',
79
+ magenta: '\x1b[35m',
80
+ cyan: '\x1b[36m',
81
+ white: '\x1b[37m',
82
+ gray: '\x1b[90m',
83
+ };
84
+
85
+ // Object pool for LogEntry reuse
86
+ private static readonly ENTRY_POOL: LogEntry[] = [];
87
+ private static readonly MAX_POOL_SIZE = 100;
88
+ private static poolIndex = 0;
89
+
90
+ // Object pool management
91
+ private static getPooledEntry(): LogEntry {
92
+ if (MoroLogger.poolIndex > 0) {
93
+ return MoroLogger.ENTRY_POOL[--MoroLogger.poolIndex];
94
+ }
95
+ return {
96
+ timestamp: new Date(),
97
+ level: 'info',
98
+ message: '',
99
+ context: undefined,
100
+ metadata: undefined,
101
+ };
102
+ }
103
+
104
+ private static returnPooledEntry(entry: LogEntry): void {
105
+ if (MoroLogger.poolIndex < MoroLogger.MAX_POOL_SIZE) {
106
+ // Reset the entry
107
+ entry.timestamp = new Date();
108
+ entry.level = 'info';
109
+ entry.message = '';
110
+ entry.context = undefined;
111
+ entry.metadata = undefined;
112
+ MoroLogger.ENTRY_POOL[MoroLogger.poolIndex++] = entry;
113
+ }
114
+ }
115
+
116
+ // String builder for efficient concatenation
117
+ private static stringBuilder: string[] = [];
118
+ private static stringBuilderIndex = 0;
119
+
120
+ private static resetStringBuilder(): void {
121
+ MoroLogger.stringBuilderIndex = 0;
122
+ }
123
+
124
+ private static appendToBuilder(str: string): void {
125
+ if (MoroLogger.stringBuilderIndex < MoroLogger.stringBuilder.length) {
126
+ MoroLogger.stringBuilder[MoroLogger.stringBuilderIndex++] = str;
127
+ } else {
128
+ MoroLogger.stringBuilder.push(str);
129
+ MoroLogger.stringBuilderIndex++;
130
+ }
131
+ }
132
+
133
+ private static buildString(): string {
134
+ const result = MoroLogger.stringBuilder.slice(0, MoroLogger.stringBuilderIndex).join('');
135
+ MoroLogger.resetStringBuilder();
136
+ return result;
137
+ }
138
+
75
139
  private static readonly COLORS: ColorScheme = {
76
140
  debug: '\x1b[36m', // Cyan
77
141
  info: '\x1b[32m', // Green
@@ -82,23 +146,13 @@ export class MoroLogger implements Logger {
82
146
  context: '\x1b[34m', // Blue
83
147
  metadata: '\x1b[37m', // White
84
148
  performance: '\x1b[36m', // Cyan
85
- reset: '\x1b[0m', // Reset
86
149
  };
87
150
 
88
151
  private static readonly RESET = '\x1b[0m';
89
152
  private static readonly BOLD = '\x1b[1m';
90
153
 
91
- // Static pre-allocated strings for performance
92
- private static readonly LEVEL_STRINGS: Record<LogLevel, string> = {
93
- debug: 'DEBUG',
94
- info: 'INFO ',
95
- warn: 'WARN ',
96
- error: 'ERROR',
97
- fatal: 'FATAL',
98
- };
99
-
100
154
  constructor(options: LoggerOptions = {}) {
101
- this.options = this.validateOptions({
155
+ this.options = {
102
156
  level: 'info',
103
157
  enableColors: true,
104
158
  enableTimestamp: true,
@@ -109,18 +163,11 @@ export class MoroLogger implements Logger {
109
163
  outputs: [],
110
164
  filters: [],
111
165
  maxEntries: 1000,
112
- maxBufferSize: 1000,
113
166
  ...options,
114
- });
167
+ };
115
168
 
116
169
  this.level = this.options.level || 'info';
117
170
 
118
- // Initialize buffer size from options
119
- this.maxBufferSize = this.options.maxBufferSize || 1000;
120
-
121
- // Initialize buffer overflow protection
122
- this.bufferOverflowThreshold = this.maxBufferSize * 2;
123
-
124
171
  // Add default console output
125
172
  this.addOutput({
126
173
  name: 'console',
@@ -133,58 +180,6 @@ export class MoroLogger implements Logger {
133
180
  this.options.filters?.forEach(filter => this.addFilter(filter));
134
181
  }
135
182
 
136
- // Object pooling methods
137
- private static getPooledEntry(): LogEntry {
138
- if (MoroLogger.ENTRY_POOL.length > 0) {
139
- const entry = MoroLogger.ENTRY_POOL.pop()!;
140
- // Properly reset ALL properties to prevent memory leaks
141
- entry.timestamp = new Date();
142
- entry.level = 'info';
143
- entry.message = '';
144
- entry.context = undefined;
145
- entry.metadata = undefined;
146
- entry.performance = undefined;
147
- entry.moduleId = undefined;
148
- return entry;
149
- }
150
- return MoroLogger.createFreshEntry();
151
- }
152
-
153
- // ADD this new method:
154
- private static createFreshEntry(): LogEntry {
155
- return {
156
- timestamp: new Date(),
157
- level: 'info',
158
- message: '',
159
- context: undefined,
160
- metadata: undefined,
161
- performance: undefined,
162
- moduleId: undefined,
163
- };
164
- }
165
-
166
- private static returnPooledEntry(entry: LogEntry): void {
167
- if (MoroLogger.ENTRY_POOL.length < MoroLogger.MAX_POOL_SIZE) {
168
- MoroLogger.ENTRY_POOL.push(entry);
169
- }
170
- }
171
-
172
- // String builder methods
173
- private static resetStringBuilder(): void {
174
- MoroLogger.stringBuilder.length = 0;
175
- MoroLogger.stringBuilderIndex = 0;
176
- }
177
-
178
- private static appendToBuilder(str: string): void {
179
- MoroLogger.stringBuilder[MoroLogger.stringBuilderIndex++] = str;
180
- }
181
-
182
- private static buildString(): string {
183
- const result = MoroLogger.stringBuilder.join('');
184
- MoroLogger.resetStringBuilder();
185
- return result;
186
- }
187
-
188
183
  debug(message: string, context?: string, metadata?: Record<string, any>): void {
189
184
  this.log('debug', message, context, metadata);
190
185
  }
@@ -227,9 +222,7 @@ export class MoroLogger implements Logger {
227
222
  }
228
223
 
229
224
  child(context: string, metadata?: Record<string, any>): Logger {
230
- // Create child logger with current parent level (not original options level)
231
- const childOptions = { ...this.options, level: this.level };
232
- const childLogger = new MoroLogger(childOptions);
225
+ const childLogger = new MoroLogger(this.options);
233
226
  childLogger.contextPrefix = this.contextPrefix ? `${this.contextPrefix}:${context}` : context;
234
227
  childLogger.contextMetadata = { ...this.contextMetadata, ...metadata };
235
228
  childLogger.outputs = this.outputs;
@@ -245,10 +238,6 @@ export class MoroLogger implements Logger {
245
238
  this.level = level;
246
239
  }
247
240
 
248
- getLevel(): LogLevel {
249
- return this.level;
250
- }
251
-
252
241
  addOutput(output: LogOutput): void {
253
242
  this.outputs.set(output.name, output);
254
243
  }
@@ -298,17 +287,6 @@ export class MoroLogger implements Logger {
298
287
  return this.cachedTimestamp;
299
288
  }
300
289
 
301
- // Cached timestamp generation (updates once per second)
302
- private getFastCachedTimestamp(): string {
303
- const now = Date.now();
304
- if (now - this.lastTimestamp > 1000) {
305
- // Update every second
306
- this.lastTimestamp = now;
307
- this.cachedTimestamp = new Date(now).toISOString().slice(0, 19).replace('T', ' ');
308
- }
309
- return this.cachedTimestamp;
310
- }
311
-
312
290
  getMetrics(): LogMetrics {
313
291
  const now = Date.now();
314
292
  const uptime = (now - this.startTime) / 1000; // seconds
@@ -336,49 +314,62 @@ export class MoroLogger implements Logger {
336
314
  };
337
315
  }
338
316
 
339
- // Optimized logging method
317
+ // Optimized logging method with aggressive level checking
340
318
  private log(
341
319
  level: LogLevel,
342
320
  message: string,
343
321
  context?: string,
344
322
  metadata?: Record<string, any>
345
323
  ): void {
346
- // Quick level check - use parent level if available (for child loggers)
347
- const effectiveLevel = this.parent ? this.parent.level : this.level;
348
- if (MoroLogger.LEVELS[level] < MoroLogger.LEVELS[effectiveLevel as LogLevel]) {
349
- return;
324
+ // AGGRESSIVE LEVEL CHECK - numeric comparison for maximum speed
325
+ const levelNum = MoroLogger.LEVELS[level];
326
+ const effectiveLevelNum = this.parent
327
+ ? MoroLogger.LEVELS[this.parent.level]
328
+ : MoroLogger.LEVELS[this.level];
329
+
330
+ if (levelNum < effectiveLevelNum) {
331
+ return; // Exit immediately if level is too low
350
332
  }
351
333
 
352
- // Absolute minimal path for simple logs - pure speed
334
+ // ULTRA-FAST PATH: Just message, no context, no metadata
353
335
  if (!metadata && !context && !this.contextPrefix && !this.contextMetadata) {
354
- this.writeSimpleLog(level, message);
336
+ const levelStr = MoroLogger.LEVEL_STRINGS[level];
337
+ this.output(`${levelStr} ${message}\n`, level);
355
338
  return;
356
339
  }
357
340
 
358
- // Minimal path for logs with context but no metadata
341
+ // FAST PATH: Message + context, no metadata
359
342
  if (!metadata && !this.contextMetadata) {
360
- this.writeSimpleLog(level, message, context);
343
+ const levelStr = MoroLogger.LEVEL_STRINGS[level];
344
+ if (context) {
345
+ this.output(`${levelStr} [${context}] ${message}\n`, level);
346
+ } else {
347
+ this.output(`${levelStr} ${message}\n`, level);
348
+ }
361
349
  return;
362
350
  }
363
351
 
364
- // Path for complex logs
365
- if (metadata && Object.keys(metadata).length > 0) {
366
- this.complexLog(level, message, context, metadata);
352
+ // MEDIUM PATH: Message + context + simple metadata
353
+ if (metadata && Object.keys(metadata).length <= 3 && !this.contextMetadata) {
354
+ const levelStr = MoroLogger.LEVEL_STRINGS[level];
355
+ const contextStr = context ? `[${context}] ` : '';
356
+ const metaStr = this.stringify(metadata);
357
+ this.output(`${levelStr} ${contextStr}${message} ${metaStr}\n`, level);
367
358
  return;
368
359
  }
369
360
 
370
- // Full logging path for complex logs
361
+ // FULL PATH: All features enabled
371
362
  this.fullLog(level, message, context, metadata);
372
363
  }
373
364
 
374
- // Full logging with all features
365
+ // Full logging with all features using object pooling
375
366
  private fullLog(
376
367
  level: LogLevel,
377
368
  message: string,
378
369
  context?: string,
379
370
  metadata?: Record<string, any>
380
371
  ): void {
381
- // Use object pooling for LogEntry (Pino's technique)
372
+ // Get pooled entry to avoid allocation
382
373
  const entry = MoroLogger.getPooledEntry();
383
374
  const now = Date.now();
384
375
 
@@ -397,7 +388,6 @@ export class MoroLogger implements Logger {
397
388
  if (this.filters.size > 0) {
398
389
  for (const filter of this.filters.values()) {
399
390
  if (!filter.filter(entry)) {
400
- MoroLogger.returnPooledEntry(entry);
401
391
  return;
402
392
  }
403
393
  }
@@ -412,89 +402,8 @@ export class MoroLogger implements Logger {
412
402
  // Write to outputs with batched processing
413
403
  this.writeToOutputs(entry, level);
414
404
 
415
- // Return entry to pool
416
- MoroLogger.returnPooledEntry(entry);
417
- }
418
-
419
- // Absolute minimal logging - pure speed, no overhead
420
- private complexLog(
421
- level: LogLevel,
422
- message: string,
423
- context?: string,
424
- metadata?: Record<string, any>
425
- ): void {
426
- // Use object pooling for LogEntry (Pino's technique)
427
- const entry = MoroLogger.getPooledEntry();
428
- const now = Date.now();
429
-
430
- entry.timestamp = new Date(now);
431
- entry.level = level;
432
- entry.message = message;
433
- entry.context = this.contextPrefix
434
- ? context
435
- ? `${this.contextPrefix}:${context}`
436
- : this.contextPrefix
437
- : context;
438
- entry.metadata = this.createMetadata(metadata);
439
- entry.performance = this.options.enablePerformance ? this.getPerformanceData(now) : undefined;
440
-
441
- // Write to outputs with batched processing
442
- this.writeToOutputs(entry, level);
443
-
444
- // Return entry to pool
445
- MoroLogger.returnPooledEntry(entry);
446
- }
447
-
448
- // Simple log writer with colors for minimal overhead cases
449
- private writeSimpleLog(level: LogLevel, message: string, context?: string): void {
450
- const colors = this.options.enableColors !== false;
451
- const levelReset = colors ? MoroLogger.RESET : '';
452
-
453
- MoroLogger.resetStringBuilder();
454
-
455
- // Timestamp with caching optimization
456
- if (this.options.enableTimestamp !== false) {
457
- const timestamp = this.getFastCachedTimestamp();
458
- if (colors) {
459
- MoroLogger.appendToBuilder(MoroLogger.COLORS.timestamp);
460
- MoroLogger.appendToBuilder(timestamp);
461
- MoroLogger.appendToBuilder(levelReset);
462
- } else {
463
- MoroLogger.appendToBuilder(timestamp);
464
- }
465
- MoroLogger.appendToBuilder(' ');
466
- }
467
-
468
- // Level with pre-allocated strings
469
- const levelStr = MoroLogger.LEVEL_STRINGS[level];
470
- if (colors) {
471
- MoroLogger.appendToBuilder(MoroLogger.COLORS[level]);
472
- MoroLogger.appendToBuilder(MoroLogger.BOLD);
473
- MoroLogger.appendToBuilder(levelStr);
474
- MoroLogger.appendToBuilder(levelReset);
475
- } else {
476
- MoroLogger.appendToBuilder(levelStr);
477
- }
478
-
479
- // Context
480
- if (context && this.options.enableContext !== false) {
481
- MoroLogger.appendToBuilder(' ');
482
- if (colors) {
483
- MoroLogger.appendToBuilder(MoroLogger.COLORS.context);
484
- MoroLogger.appendToBuilder(`[${context}]`);
485
- MoroLogger.appendToBuilder(levelReset);
486
- } else {
487
- MoroLogger.appendToBuilder(`[${context}]`);
488
- }
489
- }
490
-
491
- // Message
492
- MoroLogger.appendToBuilder(' ');
493
- MoroLogger.appendToBuilder(message);
494
-
495
- // Output main log line with high-performance method
496
- const finalMessage = MoroLogger.buildString();
497
- this.output(`${finalMessage}\n`, level);
405
+ // Return entry to pool after a short delay to allow async operations
406
+ setTimeout(() => MoroLogger.returnPooledEntry(entry), 0);
498
407
  }
499
408
 
500
409
  private updateMetrics(entry: LogEntry): void {
@@ -548,39 +457,24 @@ export class MoroLogger implements Logger {
548
457
  private writeToOutputs(entry: LogEntry, level: LogLevel): void {
549
458
  if (this.outputs.size === 0) return;
550
459
 
551
- let successCount = 0;
552
- const errors: Array<{ outputName: string; error: any }> = [];
553
-
554
460
  for (const output of this.outputs.values()) {
555
461
  if (!output.level || MoroLogger.LEVELS[level] >= MoroLogger.LEVELS[output.level]) {
556
462
  try {
557
463
  output.write(entry);
558
- successCount++;
559
464
  } catch (error) {
560
- errors.push({ outputName: output.name, error });
561
- this.handleOutputError(output.name, error);
465
+ // Fallback to console.error for logger errors
466
+ // eslint-disable-next-line no-console
467
+ console.error('Logger output error:', error);
562
468
  }
563
469
  }
564
470
  }
565
-
566
- // If all outputs fail, use emergency console
567
- if (successCount === 0 && this.outputs.size > 0) {
568
- this.emergencyConsoleWrite(entry);
569
- }
570
-
571
- // Log output errors (but avoid infinite loops)
572
- if (errors.length > 0 && level !== 'error') {
573
- this.error(`Logger output errors: ${errors.length} failed`, 'MoroLogger', {
574
- errors: errors.map(e => e.outputName),
575
- });
576
- }
577
471
  }
578
472
 
579
473
  private writeToConsole(entry: LogEntry): void {
580
474
  const format = this.options.format || 'pretty';
581
475
 
582
476
  if (format === 'json') {
583
- this.output(`${this.safeStringify(entry)}\n`, entry.level);
477
+ this.output(JSON.stringify(entry) + '\n', entry.level);
584
478
  return;
585
479
  }
586
480
 
@@ -597,8 +491,6 @@ export class MoroLogger implements Logger {
597
491
 
598
492
  private writePrettyLog(entry: LogEntry): void {
599
493
  const colors = this.options.enableColors !== false;
600
- const levelReset = colors ? MoroLogger.RESET : '';
601
-
602
494
  MoroLogger.resetStringBuilder();
603
495
 
604
496
  // Timestamp with caching optimization
@@ -607,29 +499,37 @@ export class MoroLogger implements Logger {
607
499
  if (colors) {
608
500
  MoroLogger.appendToBuilder(MoroLogger.COLORS.timestamp);
609
501
  MoroLogger.appendToBuilder(timestamp);
610
- MoroLogger.appendToBuilder(levelReset);
502
+ MoroLogger.appendToBuilder(MoroLogger.RESET);
611
503
  } else {
612
504
  MoroLogger.appendToBuilder(timestamp);
613
505
  }
506
+ }
507
+
508
+ // Level with color using pre-allocated strings
509
+ const levelColor = colors ? MoroLogger.COLORS[entry.level] : '';
510
+ const levelReset = colors ? MoroLogger.RESET : '';
511
+ const levelText = MoroLogger.LEVEL_STRINGS[entry.level];
512
+
513
+ // Add space after timestamp if present
514
+ if (this.options.enableTimestamp !== false) {
614
515
  MoroLogger.appendToBuilder(' ');
615
516
  }
616
517
 
617
- // Level with pre-allocated strings
618
- const levelStr = MoroLogger.LEVEL_STRINGS[entry.level];
619
518
  if (colors) {
620
- MoroLogger.appendToBuilder(MoroLogger.COLORS[entry.level]);
519
+ MoroLogger.appendToBuilder(levelColor);
621
520
  MoroLogger.appendToBuilder(MoroLogger.BOLD);
622
- MoroLogger.appendToBuilder(levelStr);
521
+ MoroLogger.appendToBuilder(levelText);
623
522
  MoroLogger.appendToBuilder(levelReset);
624
523
  } else {
625
- MoroLogger.appendToBuilder(levelStr);
524
+ MoroLogger.appendToBuilder(levelText);
626
525
  }
627
526
 
628
527
  // Context
629
528
  if (entry.context && this.options.enableContext !== false) {
630
- MoroLogger.appendToBuilder(' ');
529
+ const contextColor = colors ? MoroLogger.COLORS.context : '';
530
+ MoroLogger.appendToBuilder(' '); // Space before context
631
531
  if (colors) {
632
- MoroLogger.appendToBuilder(MoroLogger.COLORS.context);
532
+ MoroLogger.appendToBuilder(contextColor);
633
533
  MoroLogger.appendToBuilder(`[${entry.context}]`);
634
534
  MoroLogger.appendToBuilder(levelReset);
635
535
  } else {
@@ -638,7 +538,7 @@ export class MoroLogger implements Logger {
638
538
  }
639
539
 
640
540
  // Message
641
- MoroLogger.appendToBuilder(' ');
541
+ MoroLogger.appendToBuilder(' '); // Space before message
642
542
  MoroLogger.appendToBuilder(entry.message);
643
543
 
644
544
  // Performance info
@@ -654,7 +554,7 @@ export class MoroLogger implements Logger {
654
554
  }
655
555
 
656
556
  if (perfParts.length > 0) {
657
- MoroLogger.appendToBuilder(' ');
557
+ MoroLogger.appendToBuilder(' '); // Space before performance info
658
558
  if (colors) {
659
559
  MoroLogger.appendToBuilder(perfColor);
660
560
  MoroLogger.appendToBuilder(`(${perfParts.join(', ')})`);
@@ -675,20 +575,19 @@ export class MoroLogger implements Logger {
675
575
  const cleanMetadata = this.cleanMetadata(entry.metadata);
676
576
 
677
577
  if (Object.keys(cleanMetadata).length > 0) {
678
- MoroLogger.appendToBuilder(' ');
679
578
  if (colors) {
680
579
  MoroLogger.appendToBuilder(metaColor);
681
- MoroLogger.appendToBuilder(this.safeStringify(cleanMetadata));
580
+ MoroLogger.appendToBuilder(this.stringify(cleanMetadata));
682
581
  MoroLogger.appendToBuilder(levelReset);
683
582
  } else {
684
- MoroLogger.appendToBuilder(this.safeStringify(cleanMetadata));
583
+ MoroLogger.appendToBuilder(this.stringify(cleanMetadata));
685
584
  }
686
585
  }
687
586
  }
688
587
 
689
588
  // Output main log line with high-performance method
690
589
  const finalMessage = MoroLogger.buildString();
691
- this.output(`${finalMessage}\n`, entry.level);
590
+ this.output(finalMessage + '\n', entry.level);
692
591
 
693
592
  // Stack trace for errors
694
593
  if (entry.metadata?.stack && (entry.level === 'error' || entry.level === 'fatal')) {
@@ -708,196 +607,79 @@ export class MoroLogger implements Logger {
708
607
  return clean;
709
608
  }
710
609
 
711
- // High-performance output with buffering
712
- private output(message: string, level: LogLevel = 'info'): void {
713
- // Prevent memory exhaustion
714
- if (
715
- this.outputBuffer.length >= this.bufferOverflowThreshold &&
716
- !this.emergencyFlushInProgress
717
- ) {
718
- this.emergencyFlushInProgress = true;
719
- this.forceFlushBuffer();
720
- this.emergencyFlushInProgress = false;
610
+ // Fast JSON stringify with error handling
611
+ private stringify(obj: any): string {
612
+ try {
613
+ return JSON.stringify(obj);
614
+ } catch {
615
+ return '[Circular Reference]';
721
616
  }
617
+ }
722
618
 
619
+ // High-performance output with micro-batching
620
+ private output(message: string, level: LogLevel = 'info'): void {
621
+ // Add to buffer
723
622
  this.outputBuffer.push(message);
724
- this.bufferSize++;
623
+ this.bufferSize += message.length;
725
624
 
726
- // Immediate flush for critical levels or full buffer
727
- if (level === 'fatal' || level === 'error' || this.bufferSize >= this.maxBufferSize) {
625
+ // Flush immediately if buffer is full or for errors
626
+ if (this.bufferSize >= this.maxBufferSize || level === 'error' || level === 'fatal') {
728
627
  this.flushBuffer();
729
628
  } else {
629
+ // Schedule flush with micro-batching
730
630
  this.scheduleFlush();
731
631
  }
732
632
  }
733
633
 
734
634
  private scheduleFlush(): void {
735
- if (this.flushTimeout || this.isDestroyed) {
736
- return; // Already scheduled or destroyed
737
- }
635
+ if (this.flushTimeout) return; // Already scheduled
738
636
 
739
637
  this.flushTimeout = setTimeout(() => {
740
638
  this.flushBuffer();
639
+ this.flushTimeout = null;
741
640
  }, this.flushInterval);
742
641
  }
743
642
 
744
- public flushBuffer(): void {
745
- if (this.outputBuffer.length === 0) {
746
- return;
747
- }
748
-
749
- // Group messages by stream type
750
- const stdoutMessages: string[] = [];
751
- const stderrMessages: string[] = [];
643
+ private flushBuffer(): void {
644
+ if (this.outputBuffer.length === 0) return;
752
645
 
753
- for (const message of this.outputBuffer) {
754
- // Determine stream based on message content or level
755
- if (message.includes('ERROR') || message.includes('FATAL')) {
756
- stderrMessages.push(message);
757
- } else {
758
- stdoutMessages.push(message);
646
+ try {
647
+ // Group by stream type for efficiency
648
+ const stdoutMessages: string[] = [];
649
+ const stderrMessages: string[] = [];
650
+
651
+ for (const message of this.outputBuffer) {
652
+ // Determine stream based on message content (simple heuristic)
653
+ if (message.includes('ERROR') || message.includes('FATAL')) {
654
+ stderrMessages.push(message);
655
+ } else {
656
+ stdoutMessages.push(message);
657
+ }
759
658
  }
760
- }
761
659
 
762
- // Write to appropriate streams with error handling
763
- try {
764
- if (stdoutMessages.length > 0 && process.stdout.writable) {
660
+ // Write to streams
661
+ if (stdoutMessages.length > 0) {
765
662
  process.stdout.write(stdoutMessages.join(''));
766
663
  }
767
- if (stderrMessages.length > 0 && process.stderr.writable) {
664
+ if (stderrMessages.length > 0) {
768
665
  process.stderr.write(stderrMessages.join(''));
769
666
  }
770
667
  } catch {
771
- // Fallback to console if streams fail
772
- try {
773
- // eslint-disable-next-line no-console
774
- console.log(this.outputBuffer.join(''));
775
- } catch {
776
- // If even console.log fails, just ignore
668
+ // Fallback to console methods if stream write fails
669
+ for (const message of this.outputBuffer) {
670
+ if (message.includes('ERROR') || message.includes('FATAL')) {
671
+ // eslint-disable-next-line no-console
672
+ console.error(message.trim());
673
+ } else {
674
+ // eslint-disable-next-line no-console
675
+ console.log(message.trim());
676
+ }
777
677
  }
778
678
  }
779
679
 
780
- // Clear buffer
680
+ // Reset buffer
781
681
  this.outputBuffer.length = 0;
782
682
  this.bufferSize = 0;
783
-
784
- // Clear timeout
785
- if (this.flushTimeout) {
786
- clearTimeout(this.flushTimeout);
787
- this.flushTimeout = null;
788
- }
789
- }
790
-
791
- // Emergency flush for buffer overflow protection
792
- private forceFlushBuffer(): void {
793
- if (this.outputBuffer.length === 0) return;
794
-
795
- try {
796
- const message = this.outputBuffer.join('');
797
- process.stdout.write(message);
798
- } catch (error) {
799
- // Emergency fallback - write individual messages
800
- for (const msg of this.outputBuffer) {
801
- try {
802
- process.stdout.write(msg);
803
- } catch {
804
- // If even this fails, give up on this batch
805
- break;
806
- }
807
- }
808
- } finally {
809
- this.outputBuffer.length = 0;
810
- this.bufferSize = 0;
811
- }
812
- }
813
-
814
- // Safe stringify with circular reference detection
815
- private safeStringify(obj: any, maxDepth = 3): string {
816
- const seen = new WeakSet();
817
-
818
- const stringify = (value: any, depth: number): any => {
819
- if (depth > maxDepth) return '[Max Depth Reached]';
820
- if (value === null || typeof value !== 'object') return value;
821
- if (seen.has(value)) return '[Circular Reference]';
822
-
823
- seen.add(value);
824
-
825
- if (Array.isArray(value)) {
826
- return value.map(item => stringify(item, depth + 1));
827
- }
828
-
829
- const result: any = {};
830
- for (const [key, val] of Object.entries(value)) {
831
- if (typeof val !== 'function') {
832
- // Skip functions
833
- result[key] = stringify(val, depth + 1);
834
- }
835
- }
836
- return result;
837
- };
838
-
839
- try {
840
- return JSON.stringify(stringify(obj, 0));
841
- } catch (error) {
842
- return '[Stringify Error]';
843
- }
844
- }
845
-
846
- // Configuration validation
847
- private validateOptions(options: LoggerOptions): LoggerOptions {
848
- const validated = { ...options };
849
-
850
- // Validate log level
851
- const validLevels = ['debug', 'info', 'warn', 'error', 'fatal'];
852
- if (validated.level && !validLevels.includes(validated.level)) {
853
- console.warn(`[MoroLogger] Invalid log level: ${validated.level}, defaulting to 'info'`);
854
- validated.level = 'info';
855
- }
856
-
857
- // Validate max entries
858
- if (validated.maxEntries !== undefined) {
859
- if (validated.maxEntries < 1 || validated.maxEntries > 100000) {
860
- console.warn(
861
- `[MoroLogger] Invalid maxEntries: ${validated.maxEntries}, defaulting to 1000`
862
- );
863
- validated.maxEntries = 1000;
864
- }
865
- }
866
-
867
- // Validate buffer size
868
- if (validated.maxBufferSize !== undefined) {
869
- if (validated.maxBufferSize < 10 || validated.maxBufferSize > 10000) {
870
- console.warn(
871
- `[MoroLogger] Invalid maxBufferSize: ${validated.maxBufferSize}, defaulting to 1000`
872
- );
873
- validated.maxBufferSize = 1000;
874
- }
875
- }
876
-
877
- return validated;
878
- }
879
-
880
- // Error handling methods
881
- private handleOutputError(outputName: string, error: any): void {
882
- // Could implement output retry logic, circuit breaker, etc.
883
- // For now, just track the error
884
- if (!this.metrics.outputErrors) {
885
- this.metrics.outputErrors = {};
886
- }
887
- this.metrics.outputErrors[outputName] = (this.metrics.outputErrors[outputName] || 0) + 1;
888
- }
889
-
890
- private emergencyConsoleWrite(entry: LogEntry): void {
891
- const message = `${entry.timestamp.toISOString()} ${entry.level.toUpperCase()} ${entry.message}`;
892
- try {
893
- if (entry.level === 'error' || entry.level === 'fatal') {
894
- process.stderr.write(`[EMERGENCY] ${message}\n`);
895
- } else {
896
- process.stdout.write(`[EMERGENCY] ${message}\n`);
897
- }
898
- } catch {
899
- // If even emergency write fails, there's nothing more we can do
900
- }
901
683
  }
902
684
 
903
685
  // Force flush streams (useful for shutdown)
@@ -912,40 +694,28 @@ export class MoroLogger implements Logger {
912
694
  this.flushBuffer();
913
695
 
914
696
  try {
915
- // Force flush streams without ending them
697
+ // Force flush streams
916
698
  if (process.stdout.writable) {
917
- process.stdout.write(''); // Force flush without ending
699
+ process.stdout.end();
918
700
  }
919
701
  if (process.stderr.writable) {
920
- process.stderr.write(''); // Force flush without ending
702
+ process.stderr.end();
921
703
  }
922
704
  } catch {
923
705
  // Ignore flush errors
924
706
  }
925
707
  }
926
708
 
927
- // Destroy logger and clean up all resources (for testing)
928
- public destroy(): void {
929
- // Mark as destroyed to prevent new timeouts
930
- this.isDestroyed = true;
931
-
932
- // Clear any remaining timeouts
709
+ // Cleanup method to clear all timeouts and handles
710
+ public cleanup(): void {
711
+ // Clear any pending flush timeout
933
712
  if (this.flushTimeout) {
934
713
  clearTimeout(this.flushTimeout);
935
714
  this.flushTimeout = null;
936
715
  }
937
716
 
938
- // Flush any remaining buffer
717
+ // Flush any remaining output
939
718
  this.flushBuffer();
940
-
941
- // Clear outputs and filters
942
- this.outputs.clear();
943
- this.filters.clear();
944
-
945
- // Clear history
946
- this.history.length = 0;
947
- this.historyIndex = 0;
948
- this.historySize = 0;
949
719
  }
950
720
  }
951
721
 
@@ -961,6 +731,33 @@ export const logger = new MoroLogger({
961
731
  format: (process.env.LOG_FORMAT as any) || 'pretty',
962
732
  });
963
733
 
734
+ // Add cleanup handlers for Jest and other test runners
735
+ if (typeof process !== 'undefined') {
736
+ // Cleanup on process exit
737
+ process.on('beforeExit', () => {
738
+ logger.cleanup();
739
+ });
740
+
741
+ process.on('SIGINT', () => {
742
+ logger.cleanup();
743
+ process.exit(0);
744
+ });
745
+
746
+ process.on('SIGTERM', () => {
747
+ logger.cleanup();
748
+ process.exit(0);
749
+ });
750
+
751
+ // For Jest and other test runners - cleanup on uncaught exceptions
752
+ process.on('uncaughtException', () => {
753
+ logger.cleanup();
754
+ });
755
+
756
+ process.on('unhandledRejection', () => {
757
+ logger.cleanup();
758
+ });
759
+ }
760
+
964
761
  /**
965
762
  * Configure the global logger with new settings
966
763
  * This allows runtime configuration of the logger
@@ -973,14 +770,6 @@ export function configureGlobalLogger(options: Partial<LoggerOptions>): void {
973
770
  // For now, focusing on level which is the most critical
974
771
  }
975
772
 
976
- /**
977
- * Destroy the global logger and clean up resources (for testing)
978
- * @internal
979
- */
980
- export function destroyGlobalLogger(): void {
981
- logger.destroy();
982
- }
983
-
984
773
  /**
985
774
  * Apply logging configuration from the config system and/or createApp options
986
775
  */