@morojs/moro 1.5.4 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/config/config-manager.d.ts +44 -0
- package/dist/core/config/config-manager.js +114 -0
- package/dist/core/config/config-manager.js.map +1 -0
- package/dist/core/config/config-sources.d.ts +21 -0
- package/dist/core/config/config-sources.js +314 -0
- package/dist/core/config/config-sources.js.map +1 -0
- package/dist/core/config/config-validator.d.ts +21 -0
- package/dist/core/config/config-validator.js +737 -0
- package/dist/core/config/config-validator.js.map +1 -0
- package/dist/core/config/file-loader.d.ts +0 -5
- package/dist/core/config/file-loader.js +0 -171
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/index.d.ts +39 -10
- package/dist/core/config/index.js +66 -29
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.js +22 -31
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/utils.d.ts +9 -2
- package/dist/core/config/utils.js +19 -32
- package/dist/core/config/utils.js.map +1 -1
- package/dist/core/framework.d.ts +2 -7
- package/dist/core/framework.js +12 -5
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +12 -0
- package/dist/core/http/http-server.js +56 -0
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/http/router.d.ts +12 -0
- package/dist/core/http/router.js +114 -36
- package/dist/core/http/router.js.map +1 -1
- package/dist/core/logger/index.d.ts +1 -1
- package/dist/core/logger/index.js +2 -1
- package/dist/core/logger/index.js.map +1 -1
- package/dist/core/logger/logger.d.ts +10 -1
- package/dist/core/logger/logger.js +99 -37
- package/dist/core/logger/logger.js.map +1 -1
- package/dist/core/routing/index.d.ts +20 -0
- package/dist/core/routing/index.js +109 -11
- package/dist/core/routing/index.js.map +1 -1
- package/dist/moro.d.ts +22 -0
- package/dist/moro.js +134 -98
- package/dist/moro.js.map +1 -1
- package/dist/types/config.d.ts +39 -2
- package/dist/types/core.d.ts +22 -39
- package/dist/types/logger.d.ts +4 -0
- package/package.json +1 -1
- package/src/core/config/config-manager.ts +133 -0
- package/src/core/config/config-sources.ts +384 -0
- package/src/core/config/config-validator.ts +1035 -0
- package/src/core/config/file-loader.ts +0 -233
- package/src/core/config/index.ts +77 -32
- package/src/core/config/schema.ts +22 -31
- package/src/core/config/utils.ts +22 -29
- package/src/core/framework.ts +18 -11
- package/src/core/http/http-server.ts +66 -0
- package/src/core/http/router.ts +127 -38
- package/src/core/logger/index.ts +1 -0
- package/src/core/logger/logger.ts +109 -36
- package/src/core/routing/index.ts +116 -12
- package/src/moro.ts +159 -107
- package/src/types/config.ts +40 -2
- package/src/types/core.ts +32 -43
- package/src/types/logger.ts +6 -0
- package/dist/core/config/loader.d.ts +0 -7
- package/dist/core/config/loader.js +0 -269
- package/dist/core/config/loader.js.map +0 -1
- package/dist/core/config/validation.d.ts +0 -17
- package/dist/core/config/validation.js +0 -131
- package/dist/core/config/validation.js.map +0 -1
- package/src/core/config/loader.ts +0 -633
- package/src/core/config/validation.ts +0 -140
|
@@ -26,6 +26,11 @@ export class MoroHttpServer {
|
|
|
26
26
|
// Request handler pooling to avoid function creation overhead
|
|
27
27
|
private middlewareExecutionCache = new Map<string, Function>();
|
|
28
28
|
|
|
29
|
+
// Response caching for ultra-fast common responses
|
|
30
|
+
private responseCache = new Map<string, Buffer>();
|
|
31
|
+
private responseCacheHits = 0;
|
|
32
|
+
private responseCacheMisses = 0;
|
|
33
|
+
|
|
29
34
|
// String interning for common values (massive memory savings)
|
|
30
35
|
private static readonly INTERNED_METHODS = new Map([
|
|
31
36
|
['GET', 'GET'],
|
|
@@ -492,6 +497,20 @@ export class MoroHttpServer {
|
|
|
492
497
|
httpRes.json = async (data: any) => {
|
|
493
498
|
if (httpRes.headersSent) return;
|
|
494
499
|
|
|
500
|
+
// PERFORMANCE OPTIMIZATION: Check response cache for common patterns
|
|
501
|
+
const cacheKey = this.getResponseCacheKey(data);
|
|
502
|
+
if (cacheKey) {
|
|
503
|
+
const cachedBuffer = this.responseCache.get(cacheKey);
|
|
504
|
+
if (cachedBuffer) {
|
|
505
|
+
this.responseCacheHits++;
|
|
506
|
+
httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
507
|
+
httpRes.setHeader('Content-Length', cachedBuffer.length);
|
|
508
|
+
httpRes.end(cachedBuffer);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
this.responseCacheMisses++;
|
|
512
|
+
}
|
|
513
|
+
|
|
495
514
|
// Ultra-fast JSON serialization with zero-copy buffers
|
|
496
515
|
let jsonString: string;
|
|
497
516
|
|
|
@@ -578,6 +597,12 @@ export class MoroHttpServer {
|
|
|
578
597
|
});
|
|
579
598
|
|
|
580
599
|
httpRes.end(finalBuffer);
|
|
600
|
+
|
|
601
|
+
// PERFORMANCE OPTIMIZATION: Cache small, common responses
|
|
602
|
+
if (cacheKey && finalBuffer.length < 1024 && this.responseCache.size < 100) {
|
|
603
|
+
this.responseCache.set(cacheKey, Buffer.from(finalBuffer));
|
|
604
|
+
}
|
|
605
|
+
|
|
581
606
|
// Return buffer to pool after response (zero-copy achievement!)
|
|
582
607
|
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
583
608
|
};
|
|
@@ -953,6 +978,47 @@ export class MoroHttpServer {
|
|
|
953
978
|
getServer(): Server {
|
|
954
979
|
return this.server;
|
|
955
980
|
}
|
|
981
|
+
|
|
982
|
+
// PERFORMANCE OPTIMIZATION: Generate cache key for common response patterns
|
|
983
|
+
private getResponseCacheKey(data: any): string | null {
|
|
984
|
+
// Only cache simple, common responses
|
|
985
|
+
if (!data || typeof data !== 'object') {
|
|
986
|
+
// Simple primitives or strings - generate key
|
|
987
|
+
const key = JSON.stringify(data);
|
|
988
|
+
return key.length < 100 ? key : null;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Common API response patterns
|
|
992
|
+
if ('hello' in data && Object.keys(data).length <= 2) {
|
|
993
|
+
// Hello world type responses
|
|
994
|
+
return `hello:${JSON.stringify(data)}`;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if ('status' in data && Object.keys(data).length <= 3) {
|
|
998
|
+
// Status responses like {status: "ok", version: "1.0.0"}
|
|
999
|
+
return `status:${JSON.stringify(data)}`;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if ('success' in data && 'message' in data && Object.keys(data).length <= 3) {
|
|
1003
|
+
// Simple success/error responses
|
|
1004
|
+
return `msg:${data.success}:${data.message}`;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Don't cache complex objects
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Performance statistics
|
|
1012
|
+
getPerformanceStats() {
|
|
1013
|
+
return {
|
|
1014
|
+
responseCacheHits: this.responseCacheHits,
|
|
1015
|
+
responseCacheMisses: this.responseCacheMisses,
|
|
1016
|
+
responseCacheSize: this.responseCache.size,
|
|
1017
|
+
paramObjectPoolSize: this.paramObjectPool.length,
|
|
1018
|
+
bufferPoolSize: this.bufferPool.length,
|
|
1019
|
+
middlewareExecutionCacheSize: this.middlewareExecutionCache.size,
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
956
1022
|
}
|
|
957
1023
|
|
|
958
1024
|
// Built-in middleware
|
package/src/core/http/router.ts
CHANGED
|
@@ -12,6 +12,14 @@ export class Router {
|
|
|
12
12
|
private routes: RouteDefinition[] = [];
|
|
13
13
|
private logger = createFrameworkLogger('Router');
|
|
14
14
|
|
|
15
|
+
// Performance optimizations - O(1) static route lookup
|
|
16
|
+
private staticRoutes = new Map<string, RouteDefinition>(); // "GET:/api/users" -> route
|
|
17
|
+
private dynamicRoutes: RouteDefinition[] = []; // Routes with parameters
|
|
18
|
+
|
|
19
|
+
// Object pooling for parameters to reduce GC pressure
|
|
20
|
+
private paramObjectPool: Record<string, string>[] = [];
|
|
21
|
+
private readonly maxPoolSize = 50;
|
|
22
|
+
|
|
15
23
|
get(path: string, ...handlers: (Middleware | HttpHandler)[]): void {
|
|
16
24
|
this.addRoute('GET', path, handlers);
|
|
17
25
|
}
|
|
@@ -37,14 +45,37 @@ export class Router {
|
|
|
37
45
|
const handler = handlers.pop() as HttpHandler;
|
|
38
46
|
const middleware = handlers as Middleware[];
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
const route: RouteDefinition = {
|
|
41
49
|
method,
|
|
42
50
|
path,
|
|
43
51
|
pattern,
|
|
44
52
|
paramNames,
|
|
45
53
|
handler,
|
|
46
54
|
middleware,
|
|
47
|
-
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Add to routes array (maintain compatibility)
|
|
58
|
+
this.routes.push(route);
|
|
59
|
+
|
|
60
|
+
// Performance optimization: separate static and dynamic routes
|
|
61
|
+
const isStatic = !path.includes(':') && !path.includes('*');
|
|
62
|
+
if (isStatic && middleware.length === 0) {
|
|
63
|
+
// Static route with no middleware - use O(1) lookup
|
|
64
|
+
const routeKey = `${method}:${path}`;
|
|
65
|
+
this.staticRoutes.set(routeKey, route);
|
|
66
|
+
this.logger.debug(`Added static route: ${routeKey}`, 'FastRoute');
|
|
67
|
+
} else {
|
|
68
|
+
// Dynamic route or has middleware - needs regex matching
|
|
69
|
+
this.dynamicRoutes.push(route);
|
|
70
|
+
this.logger.debug(`Added dynamic route: ${method} ${path}`, 'DynamicRoute');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Initialize object pool on first route
|
|
74
|
+
if (this.paramObjectPool.length === 0) {
|
|
75
|
+
for (let i = 0; i < this.maxPoolSize; i++) {
|
|
76
|
+
this.paramObjectPool.push({});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
48
79
|
}
|
|
49
80
|
|
|
50
81
|
private pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
|
|
@@ -77,10 +108,30 @@ export class Router {
|
|
|
77
108
|
'Processing'
|
|
78
109
|
);
|
|
79
110
|
|
|
80
|
-
|
|
111
|
+
// PERFORMANCE OPTIMIZATION: Fast path - O(1) static route lookup first
|
|
112
|
+
const routeKey = `${req.method}:${path}`;
|
|
113
|
+
const staticRoute = this.staticRoutes.get(routeKey);
|
|
114
|
+
|
|
115
|
+
if (staticRoute) {
|
|
116
|
+
this.logger.debug(`Fast route match: ${routeKey}`, 'FastRoute');
|
|
117
|
+
|
|
118
|
+
// Static route with no middleware - execute handler directly
|
|
119
|
+
req.params = {}; // No params for static routes
|
|
120
|
+
const result = await staticRoute.handler(req, res);
|
|
121
|
+
|
|
122
|
+
// If handler returns data and response hasn't been sent, send it
|
|
123
|
+
if (result !== undefined && result !== null && !res.headersSent) {
|
|
124
|
+
res.json(result);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fallback: Dynamic route matching (with middleware support)
|
|
131
|
+
const route = this.dynamicRoutes.find(r => r.method === req.method && r.pattern.test(path));
|
|
81
132
|
|
|
82
133
|
this.logger.debug(
|
|
83
|
-
`Found route: ${!!route}${route ? ` ${route.method} ${route.path}` : ' none'}`,
|
|
134
|
+
`Found dynamic route: ${!!route}${route ? ` ${route.method} ${route.path}` : ' none'}`,
|
|
84
135
|
'RouteMatch'
|
|
85
136
|
);
|
|
86
137
|
|
|
@@ -88,54 +139,92 @@ export class Router {
|
|
|
88
139
|
return false; // Route not found
|
|
89
140
|
}
|
|
90
141
|
|
|
91
|
-
// Extract path parameters
|
|
142
|
+
// Extract path parameters using object pooling
|
|
92
143
|
const matches = path.match(route.pattern);
|
|
93
144
|
if (matches) {
|
|
94
|
-
req.params =
|
|
145
|
+
req.params = this.acquireParamObject();
|
|
95
146
|
route.paramNames.forEach((name, index) => {
|
|
96
147
|
req.params[name] = matches[index + 1];
|
|
97
148
|
});
|
|
98
149
|
}
|
|
99
150
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
result
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
try {
|
|
152
|
+
// Execute middleware
|
|
153
|
+
for (const mw of route.middleware) {
|
|
154
|
+
await new Promise<void>((resolve, reject) => {
|
|
155
|
+
let nextCalled = false;
|
|
156
|
+
|
|
157
|
+
const next = () => {
|
|
158
|
+
if (nextCalled) return;
|
|
159
|
+
nextCalled = true;
|
|
160
|
+
resolve();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const result = mw(req, res, next);
|
|
165
|
+
|
|
166
|
+
if (result instanceof Promise) {
|
|
167
|
+
result
|
|
168
|
+
.then(() => {
|
|
169
|
+
if (!nextCalled) next();
|
|
170
|
+
})
|
|
171
|
+
.catch(reject);
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
reject(error);
|
|
120
175
|
}
|
|
121
|
-
}
|
|
122
|
-
reject(error);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
176
|
+
});
|
|
126
177
|
|
|
127
|
-
|
|
128
|
-
|
|
178
|
+
if (res.headersSent) break; // Early exit if response sent
|
|
179
|
+
}
|
|
129
180
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
res.json(result);
|
|
133
|
-
}
|
|
181
|
+
// Execute handler
|
|
182
|
+
const result = await route.handler(req, res);
|
|
134
183
|
|
|
135
|
-
|
|
184
|
+
// If handler returns data and response hasn't been sent, send it
|
|
185
|
+
if (result !== undefined && result !== null && !res.headersSent) {
|
|
186
|
+
res.json(result);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return true;
|
|
190
|
+
} finally {
|
|
191
|
+
// Release parameter object back to pool
|
|
192
|
+
if (req.params && matches) {
|
|
193
|
+
this.releaseParamObject(req.params);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
136
196
|
}
|
|
137
197
|
|
|
138
198
|
getRoutes(): RouteDefinition[] {
|
|
139
199
|
return [...this.routes];
|
|
140
200
|
}
|
|
201
|
+
|
|
202
|
+
// Object pooling methods for performance optimization
|
|
203
|
+
private acquireParamObject(): Record<string, string> {
|
|
204
|
+
const obj = this.paramObjectPool.pop();
|
|
205
|
+
if (obj) {
|
|
206
|
+
// Clear the object
|
|
207
|
+
for (const key in obj) {
|
|
208
|
+
delete obj[key];
|
|
209
|
+
}
|
|
210
|
+
return obj;
|
|
211
|
+
}
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private releaseParamObject(obj: Record<string, string>): void {
|
|
216
|
+
if (this.paramObjectPool.length < this.maxPoolSize) {
|
|
217
|
+
this.paramObjectPool.push(obj);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Performance statistics for monitoring
|
|
222
|
+
getPerformanceStats() {
|
|
223
|
+
return {
|
|
224
|
+
totalRoutes: this.routes.length,
|
|
225
|
+
staticRoutes: this.staticRoutes.size,
|
|
226
|
+
dynamicRoutes: this.dynamicRoutes.length,
|
|
227
|
+
paramObjectPoolSize: this.paramObjectPool.length,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
141
230
|
}
|
package/src/core/logger/index.ts
CHANGED
|
@@ -60,6 +60,7 @@ export class MoroLogger implements Logger {
|
|
|
60
60
|
// Buffer overflow protection
|
|
61
61
|
private bufferOverflowThreshold: number;
|
|
62
62
|
private emergencyFlushInProgress = false;
|
|
63
|
+
private isDestroyed = false;
|
|
63
64
|
|
|
64
65
|
// High-performance output methods
|
|
65
66
|
|
|
@@ -226,7 +227,9 @@ export class MoroLogger implements Logger {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
child(context: string, metadata?: Record<string, any>): Logger {
|
|
229
|
-
|
|
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);
|
|
230
233
|
childLogger.contextPrefix = this.contextPrefix ? `${this.contextPrefix}:${context}` : context;
|
|
231
234
|
childLogger.contextMetadata = { ...this.contextMetadata, ...metadata };
|
|
232
235
|
childLogger.outputs = this.outputs;
|
|
@@ -242,6 +245,10 @@ export class MoroLogger implements Logger {
|
|
|
242
245
|
this.level = level;
|
|
243
246
|
}
|
|
244
247
|
|
|
248
|
+
getLevel(): LogLevel {
|
|
249
|
+
return this.level;
|
|
250
|
+
}
|
|
251
|
+
|
|
245
252
|
addOutput(output: LogOutput): void {
|
|
246
253
|
this.outputs.set(output.name, output);
|
|
247
254
|
}
|
|
@@ -344,24 +351,13 @@ export class MoroLogger implements Logger {
|
|
|
344
351
|
|
|
345
352
|
// Absolute minimal path for simple logs - pure speed
|
|
346
353
|
if (!metadata && !context && !this.contextPrefix && !this.contextMetadata) {
|
|
347
|
-
|
|
348
|
-
if (level === 'error' || level === 'fatal') {
|
|
349
|
-
process.stderr.write(logStr);
|
|
350
|
-
} else {
|
|
351
|
-
process.stdout.write(logStr);
|
|
352
|
-
}
|
|
354
|
+
this.writeSimpleLog(level, message);
|
|
353
355
|
return;
|
|
354
356
|
}
|
|
355
357
|
|
|
356
358
|
// Minimal path for logs with context but no metadata
|
|
357
359
|
if (!metadata && !this.contextMetadata) {
|
|
358
|
-
|
|
359
|
-
const logStr = level.toUpperCase() + ' ' + contextStr + message + '\n';
|
|
360
|
-
if (level === 'error' || level === 'fatal') {
|
|
361
|
-
process.stderr.write(logStr);
|
|
362
|
-
} else {
|
|
363
|
-
process.stdout.write(logStr);
|
|
364
|
-
}
|
|
360
|
+
this.writeSimpleLog(level, message, context);
|
|
365
361
|
return;
|
|
366
362
|
}
|
|
367
363
|
|
|
@@ -427,33 +423,78 @@ export class MoroLogger implements Logger {
|
|
|
427
423
|
context?: string,
|
|
428
424
|
metadata?: Record<string, any>
|
|
429
425
|
): void {
|
|
430
|
-
//
|
|
431
|
-
|
|
426
|
+
// Use object pooling for LogEntry (Pino's technique)
|
|
427
|
+
const entry = MoroLogger.getPooledEntry();
|
|
428
|
+
const now = Date.now();
|
|
432
429
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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;
|
|
438
440
|
|
|
439
|
-
|
|
441
|
+
// Write to outputs with batched processing
|
|
442
|
+
this.writeToOutputs(entry, level);
|
|
440
443
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
+
// Return entry to pool
|
|
445
|
+
MoroLogger.returnPooledEntry(entry);
|
|
446
|
+
}
|
|
444
447
|
|
|
445
|
-
|
|
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 : '';
|
|
448
452
|
|
|
449
|
-
|
|
453
|
+
MoroLogger.resetStringBuilder();
|
|
450
454
|
|
|
451
|
-
//
|
|
452
|
-
if (
|
|
453
|
-
|
|
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);
|
|
454
475
|
} else {
|
|
455
|
-
|
|
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
|
+
}
|
|
456
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);
|
|
457
498
|
}
|
|
458
499
|
|
|
459
500
|
private updateMetrics(entry: LogEntry): void {
|
|
@@ -691,8 +732,8 @@ export class MoroLogger implements Logger {
|
|
|
691
732
|
}
|
|
692
733
|
|
|
693
734
|
private scheduleFlush(): void {
|
|
694
|
-
if (this.flushTimeout) {
|
|
695
|
-
return; // Already scheduled
|
|
735
|
+
if (this.flushTimeout || this.isDestroyed) {
|
|
736
|
+
return; // Already scheduled or destroyed
|
|
696
737
|
}
|
|
697
738
|
|
|
698
739
|
this.flushTimeout = setTimeout(() => {
|
|
@@ -700,7 +741,7 @@ export class MoroLogger implements Logger {
|
|
|
700
741
|
}, this.flushInterval);
|
|
701
742
|
}
|
|
702
743
|
|
|
703
|
-
|
|
744
|
+
public flushBuffer(): void {
|
|
704
745
|
if (this.outputBuffer.length === 0) {
|
|
705
746
|
return;
|
|
706
747
|
}
|
|
@@ -882,6 +923,30 @@ export class MoroLogger implements Logger {
|
|
|
882
923
|
// Ignore flush errors
|
|
883
924
|
}
|
|
884
925
|
}
|
|
926
|
+
|
|
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
|
|
933
|
+
if (this.flushTimeout) {
|
|
934
|
+
clearTimeout(this.flushTimeout);
|
|
935
|
+
this.flushTimeout = null;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Flush any remaining buffer
|
|
939
|
+
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
|
+
}
|
|
885
950
|
}
|
|
886
951
|
|
|
887
952
|
// Global logger instance
|
|
@@ -908,6 +973,14 @@ export function configureGlobalLogger(options: Partial<LoggerOptions>): void {
|
|
|
908
973
|
// For now, focusing on level which is the most critical
|
|
909
974
|
}
|
|
910
975
|
|
|
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
|
+
|
|
911
984
|
/**
|
|
912
985
|
* Apply logging configuration from the config system and/or createApp options
|
|
913
986
|
*/
|