claude-mpm 4.2.13__py3-none-any.whl → 4.2.15__py3-none-any.whl
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.
- claude_mpm/cli/commands/analyze_code.py +0 -1
- claude_mpm/cli/commands/dashboard.py +0 -1
- claude_mpm/cli/commands/monitor.py +0 -1
- claude_mpm/core/constants.py +65 -0
- claude_mpm/core/error_handler.py +625 -0
- claude_mpm/core/file_utils.py +770 -0
- claude_mpm/core/logging_utils.py +502 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/code-simple.js +44 -1
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +353 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +235 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +409 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +435 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +29 -5
- claude_mpm/dashboard/static/js/components/file-viewer.js +69 -27
- claude_mpm/dashboard/static/js/components/session-manager.js +7 -7
- claude_mpm/dashboard/static/js/components/working-directory.js +18 -9
- claude_mpm/dashboard/static/js/dashboard.js +91 -9
- claude_mpm/dashboard/static/js/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/js/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/js/shared/logger.js +385 -0
- claude_mpm/dashboard/static/js/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/js/socket-client.js +18 -0
- claude_mpm/dashboard/templates/index.html +22 -9
- claude_mpm/services/cli/unified_dashboard_manager.py +2 -1
- claude_mpm/services/monitor/handlers/__init__.py +2 -1
- claude_mpm/services/monitor/handlers/file.py +263 -0
- claude_mpm/services/monitor/handlers/hooks.py +25 -1
- claude_mpm/services/monitor/server.py +111 -1
- claude_mpm/services/socketio/handlers/file.py +40 -5
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/RECORD +39 -27
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Bus Service
|
|
3
|
+
*
|
|
4
|
+
* Central event management for decoupled component communication.
|
|
5
|
+
* Implements a simple pub/sub pattern for dashboard components.
|
|
6
|
+
*
|
|
7
|
+
* @module event-bus
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class EventBus {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.events = new Map();
|
|
13
|
+
this.onceEvents = new Map();
|
|
14
|
+
this.eventHistory = [];
|
|
15
|
+
this.maxHistorySize = 100;
|
|
16
|
+
this.debugMode = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to an event
|
|
21
|
+
* @param {string} event - Event name
|
|
22
|
+
* @param {Function} handler - Event handler function
|
|
23
|
+
* @param {Object} options - Subscription options
|
|
24
|
+
* @returns {Function} Unsubscribe function
|
|
25
|
+
*/
|
|
26
|
+
on(event, handler, options = {}) {
|
|
27
|
+
if (typeof handler !== 'function') {
|
|
28
|
+
throw new Error('Handler must be a function');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Initialize event handlers array if needed
|
|
32
|
+
if (!this.events.has(event)) {
|
|
33
|
+
this.events.set(event, []);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Create handler wrapper with context and priority
|
|
37
|
+
const wrapper = {
|
|
38
|
+
handler,
|
|
39
|
+
context: options.context || null,
|
|
40
|
+
priority: options.priority || 0,
|
|
41
|
+
id: this.generateId()
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Add to handlers array (sorted by priority)
|
|
45
|
+
const handlers = this.events.get(event);
|
|
46
|
+
handlers.push(wrapper);
|
|
47
|
+
handlers.sort((a, b) => b.priority - a.priority);
|
|
48
|
+
|
|
49
|
+
// Log subscription if debugging
|
|
50
|
+
if (this.debugMode) {
|
|
51
|
+
console.log(`[EventBus] Subscribed to "${event}" with handler ${wrapper.id}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Return unsubscribe function
|
|
55
|
+
return () => this.off(event, wrapper.id);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Subscribe to an event only once
|
|
60
|
+
* @param {string} event - Event name
|
|
61
|
+
* @param {Function} handler - Event handler function
|
|
62
|
+
* @param {Object} options - Subscription options
|
|
63
|
+
* @returns {Function} Unsubscribe function
|
|
64
|
+
*/
|
|
65
|
+
once(event, handler, options = {}) {
|
|
66
|
+
const wrappedHandler = (...args) => {
|
|
67
|
+
handler(...args);
|
|
68
|
+
this.off(event, wrappedHandler);
|
|
69
|
+
};
|
|
70
|
+
wrappedHandler._originalHandler = handler;
|
|
71
|
+
return this.on(event, wrappedHandler, options);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Unsubscribe from an event
|
|
76
|
+
* @param {string} event - Event name
|
|
77
|
+
* @param {string|Function} handlerOrId - Handler function or ID
|
|
78
|
+
*/
|
|
79
|
+
off(event, handlerOrId) {
|
|
80
|
+
if (!this.events.has(event)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const handlers = this.events.get(event);
|
|
85
|
+
const index = handlers.findIndex(wrapper =>
|
|
86
|
+
wrapper.id === handlerOrId ||
|
|
87
|
+
wrapper.handler === handlerOrId ||
|
|
88
|
+
wrapper.handler._originalHandler === handlerOrId
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (index !== -1) {
|
|
92
|
+
const removed = handlers.splice(index, 1)[0];
|
|
93
|
+
if (this.debugMode) {
|
|
94
|
+
console.log(`[EventBus] Unsubscribed from "${event}" handler ${removed.id}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Clean up empty event arrays
|
|
99
|
+
if (handlers.length === 0) {
|
|
100
|
+
this.events.delete(event);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Emit an event
|
|
106
|
+
* @param {string} event - Event name
|
|
107
|
+
* @param {...any} args - Arguments to pass to handlers
|
|
108
|
+
* @returns {Array} Results from all handlers
|
|
109
|
+
*/
|
|
110
|
+
emit(event, ...args) {
|
|
111
|
+
const results = [];
|
|
112
|
+
|
|
113
|
+
// Record in history
|
|
114
|
+
this.recordEvent(event, args);
|
|
115
|
+
|
|
116
|
+
// Log emission if debugging
|
|
117
|
+
if (this.debugMode) {
|
|
118
|
+
console.log(`[EventBus] Emitting "${event}" with args:`, args);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Call handlers for this specific event
|
|
122
|
+
if (this.events.has(event)) {
|
|
123
|
+
const handlers = this.events.get(event).slice(); // Clone to prevent modification during iteration
|
|
124
|
+
for (const wrapper of handlers) {
|
|
125
|
+
try {
|
|
126
|
+
const result = wrapper.context
|
|
127
|
+
? wrapper.handler.call(wrapper.context, ...args)
|
|
128
|
+
: wrapper.handler(...args);
|
|
129
|
+
results.push(result);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(`[EventBus] Error in handler for "${event}":`, error);
|
|
132
|
+
if (this.debugMode) {
|
|
133
|
+
console.error('Handler details:', wrapper);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Call wildcard handlers
|
|
140
|
+
if (this.events.has('*')) {
|
|
141
|
+
const wildcardHandlers = this.events.get('*').slice();
|
|
142
|
+
for (const wrapper of wildcardHandlers) {
|
|
143
|
+
try {
|
|
144
|
+
const result = wrapper.context
|
|
145
|
+
? wrapper.handler.call(wrapper.context, event, ...args)
|
|
146
|
+
: wrapper.handler(event, ...args);
|
|
147
|
+
results.push(result);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(`[EventBus] Error in wildcard handler for "${event}":`, error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return results;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Emit an event asynchronously
|
|
159
|
+
* @param {string} event - Event name
|
|
160
|
+
* @param {...any} args - Arguments to pass to handlers
|
|
161
|
+
* @returns {Promise<Array>} Promise resolving to results from all handlers
|
|
162
|
+
*/
|
|
163
|
+
async emitAsync(event, ...args) {
|
|
164
|
+
return new Promise(resolve => {
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
resolve(this.emit(event, ...args));
|
|
167
|
+
}, 0);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Wait for an event to occur
|
|
173
|
+
* @param {string} event - Event name to wait for
|
|
174
|
+
* @param {number} timeout - Timeout in milliseconds (optional)
|
|
175
|
+
* @returns {Promise} Promise resolving when event occurs
|
|
176
|
+
*/
|
|
177
|
+
waitFor(event, timeout) {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
let timeoutId;
|
|
180
|
+
|
|
181
|
+
const handler = (...args) => {
|
|
182
|
+
if (timeoutId) {
|
|
183
|
+
clearTimeout(timeoutId);
|
|
184
|
+
}
|
|
185
|
+
resolve(args);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
this.once(event, handler);
|
|
189
|
+
|
|
190
|
+
if (timeout) {
|
|
191
|
+
timeoutId = setTimeout(() => {
|
|
192
|
+
this.off(event, handler);
|
|
193
|
+
reject(new Error(`Timeout waiting for event "${event}"`));
|
|
194
|
+
}, timeout);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Clear all handlers for an event
|
|
201
|
+
* @param {string} event - Event name (optional, clears all if not provided)
|
|
202
|
+
*/
|
|
203
|
+
clear(event) {
|
|
204
|
+
if (event) {
|
|
205
|
+
this.events.delete(event);
|
|
206
|
+
if (this.debugMode) {
|
|
207
|
+
console.log(`[EventBus] Cleared all handlers for "${event}"`);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
this.events.clear();
|
|
211
|
+
if (this.debugMode) {
|
|
212
|
+
console.log('[EventBus] Cleared all event handlers');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get all registered events
|
|
219
|
+
* @returns {Array} Array of event names
|
|
220
|
+
*/
|
|
221
|
+
getEvents() {
|
|
222
|
+
return Array.from(this.events.keys());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get handler count for an event
|
|
227
|
+
* @param {string} event - Event name
|
|
228
|
+
* @returns {number} Number of handlers
|
|
229
|
+
*/
|
|
230
|
+
getHandlerCount(event) {
|
|
231
|
+
return this.events.has(event) ? this.events.get(event).length : 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if event has handlers
|
|
236
|
+
* @param {string} event - Event name
|
|
237
|
+
* @returns {boolean} Whether event has handlers
|
|
238
|
+
*/
|
|
239
|
+
hasHandlers(event) {
|
|
240
|
+
return this.getHandlerCount(event) > 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Enable or disable debug mode
|
|
245
|
+
* @param {boolean} enabled - Debug mode state
|
|
246
|
+
*/
|
|
247
|
+
setDebugMode(enabled) {
|
|
248
|
+
this.debugMode = enabled;
|
|
249
|
+
if (enabled) {
|
|
250
|
+
console.log('[EventBus] Debug mode enabled');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get event history
|
|
256
|
+
* @param {string} event - Optional event name filter
|
|
257
|
+
* @returns {Array} Event history
|
|
258
|
+
*/
|
|
259
|
+
getHistory(event) {
|
|
260
|
+
if (event) {
|
|
261
|
+
return this.eventHistory.filter(entry => entry.event === event);
|
|
262
|
+
}
|
|
263
|
+
return this.eventHistory.slice();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Clear event history
|
|
268
|
+
*/
|
|
269
|
+
clearHistory() {
|
|
270
|
+
this.eventHistory = [];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Record event in history
|
|
275
|
+
* @private
|
|
276
|
+
* @param {string} event - Event name
|
|
277
|
+
* @param {Array} args - Event arguments
|
|
278
|
+
*/
|
|
279
|
+
recordEvent(event, args) {
|
|
280
|
+
this.eventHistory.push({
|
|
281
|
+
event,
|
|
282
|
+
args: args.slice(),
|
|
283
|
+
timestamp: Date.now()
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Limit history size
|
|
287
|
+
if (this.eventHistory.length > this.maxHistorySize) {
|
|
288
|
+
this.eventHistory.shift();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Generate unique ID
|
|
294
|
+
* @private
|
|
295
|
+
* @returns {string} Unique ID
|
|
296
|
+
*/
|
|
297
|
+
generateId() {
|
|
298
|
+
return `handler_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Create a scoped event bus
|
|
303
|
+
* @param {string} scope - Scope prefix
|
|
304
|
+
* @returns {Object} Scoped event bus interface
|
|
305
|
+
*/
|
|
306
|
+
createScope(scope) {
|
|
307
|
+
const prefix = scope + ':';
|
|
308
|
+
return {
|
|
309
|
+
on: (event, handler, options) => this.on(prefix + event, handler, options),
|
|
310
|
+
once: (event, handler, options) => this.once(prefix + event, handler, options),
|
|
311
|
+
off: (event, handler) => this.off(prefix + event, handler),
|
|
312
|
+
emit: (event, ...args) => this.emit(prefix + event, ...args),
|
|
313
|
+
emitAsync: (event, ...args) => this.emitAsync(prefix + event, ...args),
|
|
314
|
+
clear: (event) => this.clear(event ? prefix + event : undefined),
|
|
315
|
+
hasHandlers: (event) => this.hasHandlers(prefix + event)
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Create singleton instance
|
|
321
|
+
const eventBus = new EventBus();
|
|
322
|
+
|
|
323
|
+
// Support both module and global usage
|
|
324
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
325
|
+
module.exports = eventBus;
|
|
326
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
327
|
+
define([], () => eventBus);
|
|
328
|
+
} else {
|
|
329
|
+
window.eventBus = eventBus;
|
|
330
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Service
|
|
3
|
+
*
|
|
4
|
+
* Centralized logging service with levels, formatting, and performance timing.
|
|
5
|
+
* Provides consistent logging across dashboard components.
|
|
6
|
+
*
|
|
7
|
+
* @module logger
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class Logger {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.logLevels = {
|
|
13
|
+
DEBUG: 0,
|
|
14
|
+
INFO: 1,
|
|
15
|
+
WARN: 2,
|
|
16
|
+
ERROR: 3,
|
|
17
|
+
NONE: 4
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
this.currentLevel = this.logLevels.INFO;
|
|
21
|
+
this.enableTimestamps = true;
|
|
22
|
+
this.enableColors = true;
|
|
23
|
+
this.logHistory = [];
|
|
24
|
+
this.maxHistorySize = 500;
|
|
25
|
+
this.performanceMarks = new Map();
|
|
26
|
+
this.componentLoggers = new Map();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set the current log level
|
|
31
|
+
* @param {string|number} level - Log level name or value
|
|
32
|
+
*/
|
|
33
|
+
setLevel(level) {
|
|
34
|
+
if (typeof level === 'string') {
|
|
35
|
+
level = level.toUpperCase();
|
|
36
|
+
if (level in this.logLevels) {
|
|
37
|
+
this.currentLevel = this.logLevels[level];
|
|
38
|
+
}
|
|
39
|
+
} else if (typeof level === 'number') {
|
|
40
|
+
this.currentLevel = level;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a logger for a specific component
|
|
46
|
+
* @param {string} componentName - Name of the component
|
|
47
|
+
* @returns {Object} Component logger instance
|
|
48
|
+
*/
|
|
49
|
+
createComponentLogger(componentName) {
|
|
50
|
+
if (this.componentLoggers.has(componentName)) {
|
|
51
|
+
return this.componentLoggers.get(componentName);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const componentLogger = {
|
|
55
|
+
debug: (...args) => this.debug(`[${componentName}]`, ...args),
|
|
56
|
+
info: (...args) => this.info(`[${componentName}]`, ...args),
|
|
57
|
+
warn: (...args) => this.warn(`[${componentName}]`, ...args),
|
|
58
|
+
error: (...args) => this.error(`[${componentName}]`, ...args),
|
|
59
|
+
time: (label) => this.time(`${componentName}:${label}`),
|
|
60
|
+
timeEnd: (label) => this.timeEnd(`${componentName}:${label}`),
|
|
61
|
+
group: (label) => this.group(`${componentName}: ${label}`),
|
|
62
|
+
groupEnd: () => this.groupEnd()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.componentLoggers.set(componentName, componentLogger);
|
|
66
|
+
return componentLogger;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Debug level logging
|
|
71
|
+
* @param {...any} args - Arguments to log
|
|
72
|
+
*/
|
|
73
|
+
debug(...args) {
|
|
74
|
+
if (this.currentLevel <= this.logLevels.DEBUG) {
|
|
75
|
+
this.log('DEBUG', args, '#6c757d');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Info level logging
|
|
81
|
+
* @param {...any} args - Arguments to log
|
|
82
|
+
*/
|
|
83
|
+
info(...args) {
|
|
84
|
+
if (this.currentLevel <= this.logLevels.INFO) {
|
|
85
|
+
this.log('INFO', args, '#0d6efd');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Warning level logging
|
|
91
|
+
* @param {...any} args - Arguments to log
|
|
92
|
+
*/
|
|
93
|
+
warn(...args) {
|
|
94
|
+
if (this.currentLevel <= this.logLevels.WARN) {
|
|
95
|
+
this.log('WARN', args, '#ffc107');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Error level logging
|
|
101
|
+
* @param {...any} args - Arguments to log
|
|
102
|
+
*/
|
|
103
|
+
error(...args) {
|
|
104
|
+
if (this.currentLevel <= this.logLevels.ERROR) {
|
|
105
|
+
this.log('ERROR', args, '#dc3545');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Core logging function
|
|
111
|
+
* @private
|
|
112
|
+
* @param {string} level - Log level
|
|
113
|
+
* @param {Array} args - Arguments to log
|
|
114
|
+
* @param {string} color - Color for the log level
|
|
115
|
+
*/
|
|
116
|
+
log(level, args, color) {
|
|
117
|
+
const timestamp = this.enableTimestamps ? new Date().toISOString() : '';
|
|
118
|
+
const prefix = this.formatPrefix(level, timestamp, color);
|
|
119
|
+
|
|
120
|
+
// Console output
|
|
121
|
+
const method = level === 'ERROR' ? 'error' : level === 'WARN' ? 'warn' : 'log';
|
|
122
|
+
if (this.enableColors && color) {
|
|
123
|
+
console[method](`%c${prefix}`, `color: ${color}; font-weight: bold;`, ...args);
|
|
124
|
+
} else {
|
|
125
|
+
console[method](prefix, ...args);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Add to history
|
|
129
|
+
this.addToHistory(level, timestamp, args);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Format log prefix
|
|
134
|
+
* @private
|
|
135
|
+
* @param {string} level - Log level
|
|
136
|
+
* @param {string} timestamp - Timestamp
|
|
137
|
+
* @param {string} color - Color
|
|
138
|
+
* @returns {string} Formatted prefix
|
|
139
|
+
*/
|
|
140
|
+
formatPrefix(level, timestamp, color) {
|
|
141
|
+
const parts = [];
|
|
142
|
+
if (timestamp) {
|
|
143
|
+
parts.push(`[${timestamp}]`);
|
|
144
|
+
}
|
|
145
|
+
parts.push(`[${level}]`);
|
|
146
|
+
return parts.join(' ');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Add log entry to history
|
|
151
|
+
* @private
|
|
152
|
+
* @param {string} level - Log level
|
|
153
|
+
* @param {string} timestamp - Timestamp
|
|
154
|
+
* @param {Array} args - Log arguments
|
|
155
|
+
*/
|
|
156
|
+
addToHistory(level, timestamp, args) {
|
|
157
|
+
this.logHistory.push({
|
|
158
|
+
level,
|
|
159
|
+
timestamp,
|
|
160
|
+
message: args.map(arg =>
|
|
161
|
+
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
|
|
162
|
+
).join(' ')
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Limit history size
|
|
166
|
+
if (this.logHistory.length > this.maxHistorySize) {
|
|
167
|
+
this.logHistory.shift();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Start a performance timer
|
|
173
|
+
* @param {string} label - Timer label
|
|
174
|
+
*/
|
|
175
|
+
time(label) {
|
|
176
|
+
this.performanceMarks.set(label, {
|
|
177
|
+
start: performance.now(),
|
|
178
|
+
memory: performance.memory ? performance.memory.usedJSHeapSize : null
|
|
179
|
+
});
|
|
180
|
+
this.debug(`Timer started: ${label}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* End a performance timer and log the result
|
|
185
|
+
* @param {string} label - Timer label
|
|
186
|
+
* @returns {number} Elapsed time in milliseconds
|
|
187
|
+
*/
|
|
188
|
+
timeEnd(label) {
|
|
189
|
+
const mark = this.performanceMarks.get(label);
|
|
190
|
+
if (!mark) {
|
|
191
|
+
this.warn(`Timer not found: ${label}`);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const elapsed = performance.now() - mark.start;
|
|
196
|
+
const memoryDelta = performance.memory
|
|
197
|
+
? performance.memory.usedJSHeapSize - mark.memory
|
|
198
|
+
: null;
|
|
199
|
+
|
|
200
|
+
this.performanceMarks.delete(label);
|
|
201
|
+
|
|
202
|
+
const message = [`Timer ended: ${label} - ${elapsed.toFixed(2)}ms`];
|
|
203
|
+
if (memoryDelta !== null) {
|
|
204
|
+
message.push(`(Memory: ${this.formatBytes(memoryDelta)})`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.info(...message);
|
|
208
|
+
return elapsed;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Log a performance mark
|
|
213
|
+
* @param {string} name - Mark name
|
|
214
|
+
*/
|
|
215
|
+
mark(name) {
|
|
216
|
+
if (performance.mark) {
|
|
217
|
+
performance.mark(name);
|
|
218
|
+
this.debug(`Performance mark: ${name}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Log a performance measure
|
|
224
|
+
* @param {string} name - Measure name
|
|
225
|
+
* @param {string} startMark - Start mark name
|
|
226
|
+
* @param {string} endMark - End mark name
|
|
227
|
+
*/
|
|
228
|
+
measure(name, startMark, endMark) {
|
|
229
|
+
if (performance.measure) {
|
|
230
|
+
try {
|
|
231
|
+
performance.measure(name, startMark, endMark);
|
|
232
|
+
const entries = performance.getEntriesByName(name);
|
|
233
|
+
if (entries.length > 0) {
|
|
234
|
+
const duration = entries[entries.length - 1].duration;
|
|
235
|
+
this.info(`Performance measure: ${name} - ${duration.toFixed(2)}ms`);
|
|
236
|
+
}
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.error(`Failed to measure performance: ${error.message}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Create a log group
|
|
245
|
+
* @param {string} label - Group label
|
|
246
|
+
*/
|
|
247
|
+
group(label) {
|
|
248
|
+
if (console.group) {
|
|
249
|
+
console.group(label);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create a collapsed log group
|
|
255
|
+
* @param {string} label - Group label
|
|
256
|
+
*/
|
|
257
|
+
groupCollapsed(label) {
|
|
258
|
+
if (console.groupCollapsed) {
|
|
259
|
+
console.groupCollapsed(label);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* End a log group
|
|
265
|
+
*/
|
|
266
|
+
groupEnd() {
|
|
267
|
+
if (console.groupEnd) {
|
|
268
|
+
console.groupEnd();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Log a table
|
|
274
|
+
* @param {Array|Object} data - Data to display as table
|
|
275
|
+
* @param {Array} columns - Optional column names
|
|
276
|
+
*/
|
|
277
|
+
table(data, columns) {
|
|
278
|
+
if (console.table) {
|
|
279
|
+
console.table(data, columns);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Clear the console
|
|
285
|
+
*/
|
|
286
|
+
clear() {
|
|
287
|
+
if (console.clear) {
|
|
288
|
+
console.clear();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get log history
|
|
294
|
+
* @param {string} level - Optional level filter
|
|
295
|
+
* @returns {Array} Log history
|
|
296
|
+
*/
|
|
297
|
+
getHistory(level) {
|
|
298
|
+
if (level) {
|
|
299
|
+
return this.logHistory.filter(entry => entry.level === level);
|
|
300
|
+
}
|
|
301
|
+
return this.logHistory.slice();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Export log history as text
|
|
306
|
+
* @returns {string} Log history as text
|
|
307
|
+
*/
|
|
308
|
+
exportHistory() {
|
|
309
|
+
return this.logHistory
|
|
310
|
+
.map(entry => `${entry.timestamp} [${entry.level}] ${entry.message}`)
|
|
311
|
+
.join('\n');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Clear log history
|
|
316
|
+
*/
|
|
317
|
+
clearHistory() {
|
|
318
|
+
this.logHistory = [];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Format bytes for display
|
|
323
|
+
* @private
|
|
324
|
+
* @param {number} bytes - Number of bytes
|
|
325
|
+
* @returns {string} Formatted string
|
|
326
|
+
*/
|
|
327
|
+
formatBytes(bytes) {
|
|
328
|
+
const sign = bytes < 0 ? '-' : '+';
|
|
329
|
+
bytes = Math.abs(bytes);
|
|
330
|
+
|
|
331
|
+
if (bytes === 0) return '0 B';
|
|
332
|
+
|
|
333
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
334
|
+
const index = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
335
|
+
const value = bytes / Math.pow(1024, index);
|
|
336
|
+
|
|
337
|
+
return `${sign}${value.toFixed(2)} ${units[index]}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Assert a condition
|
|
342
|
+
* @param {boolean} condition - Condition to assert
|
|
343
|
+
* @param {string} message - Error message if condition is false
|
|
344
|
+
*/
|
|
345
|
+
assert(condition, message) {
|
|
346
|
+
if (!condition) {
|
|
347
|
+
this.error(`Assertion failed: ${message}`);
|
|
348
|
+
if (console.trace) {
|
|
349
|
+
console.trace();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Count occurrences
|
|
356
|
+
* @param {string} label - Counter label
|
|
357
|
+
*/
|
|
358
|
+
count(label = 'default') {
|
|
359
|
+
if (console.count) {
|
|
360
|
+
console.count(label);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Reset a counter
|
|
366
|
+
* @param {string} label - Counter label
|
|
367
|
+
*/
|
|
368
|
+
countReset(label = 'default') {
|
|
369
|
+
if (console.countReset) {
|
|
370
|
+
console.countReset(label);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Create singleton instance
|
|
376
|
+
const logger = new Logger();
|
|
377
|
+
|
|
378
|
+
// Support both module and global usage
|
|
379
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
380
|
+
module.exports = logger;
|
|
381
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
382
|
+
define([], () => logger);
|
|
383
|
+
} else {
|
|
384
|
+
window.logger = logger;
|
|
385
|
+
}
|