vanillaforge 1.9.0
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/CHANGELOG.md +466 -0
- package/README.md +198 -0
- package/package.json +91 -0
- package/src/components/base-component.js +925 -0
- package/src/core/component-manager.js +306 -0
- package/src/core/dom-morph.js +234 -0
- package/src/core/event-bus.js +229 -0
- package/src/core/router.js +487 -0
- package/src/core/signal.js +114 -0
- package/src/framework.js +323 -0
- package/src/plugins/alerts/alerts-plugin.js +427 -0
- package/src/plugins/fonts/files/inter.js +4 -0
- package/src/plugins/fonts/files/jetbrains-mono.js +4 -0
- package/src/plugins/fonts/font-manifests.js +53 -0
- package/src/plugins/fonts/fonts-plugin.js +246 -0
- package/src/plugins/icons/default-icons.js +51 -0
- package/src/plugins/icons/icons-plugin.js +130 -0
- package/src/plugins/store/store-plugin.js +127 -0
- package/src/plugins/theme/base-styles.js +58 -0
- package/src/plugins/theme/theme-plugin.js +160 -0
- package/src/utils/decorators.js +51 -0
- package/src/utils/dom.js +40 -0
- package/src/utils/error-handler.js +442 -0
- package/src/utils/framework-debug.js +375 -0
- package/src/utils/logger.js +324 -0
- package/src/utils/notification.js +123 -0
- package/src/utils/performance.js +281 -0
- package/src/utils/storage.js +86 -0
- package/src/utils/sweet-alert.js +84 -0
- package/src/utils/validation.js +70 -0
- package/src/utils/validators.js +129 -0
- package/types/index.d.ts +524 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework Debug Utility
|
|
3
|
+
*
|
|
4
|
+
* Debugging utilities for the VanillaForge framework.
|
|
5
|
+
* Provides insights into component state, event bus activity, and performance.
|
|
6
|
+
*
|
|
7
|
+
* @author VanillaForge Team
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
* @since 2025-06-15
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Logger } from './logger.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Framework Debug class for development debugging
|
|
16
|
+
*/
|
|
17
|
+
export class FrameworkDebug {
|
|
18
|
+
constructor(app) {
|
|
19
|
+
this.app = app;
|
|
20
|
+
this.logger = new Logger('FrameworkDebug');
|
|
21
|
+
this.isEnabled = false;
|
|
22
|
+
this.performanceMarks = new Map();
|
|
23
|
+
|
|
24
|
+
this.logger.info('Framework debug utility initialized');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Enable debug mode
|
|
29
|
+
*/ enable() {
|
|
30
|
+
this.isEnabled = true;
|
|
31
|
+
|
|
32
|
+
// Enable event bus debug mode
|
|
33
|
+
if (this.app.eventBus) {
|
|
34
|
+
this.app.eventBus.setDebugMode(true);
|
|
35
|
+
}
|
|
36
|
+
// Add debug tools to window for console access
|
|
37
|
+
window.VanillaForgeDebug = this;
|
|
38
|
+
|
|
39
|
+
this.logger.info('Framework debug mode enabled');
|
|
40
|
+
|
|
41
|
+
// Only show console message in development
|
|
42
|
+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
43
|
+
console.log('VanillaForge Debug mode enabled. Access via window.VanillaForgeDebug');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Disable debug mode
|
|
49
|
+
*/
|
|
50
|
+
disable() {
|
|
51
|
+
this.isEnabled = false;
|
|
52
|
+
|
|
53
|
+
// Disable event bus debug mode
|
|
54
|
+
if (this.app.eventBus) {
|
|
55
|
+
this.app.eventBus.setDebugMode(false);
|
|
56
|
+
}
|
|
57
|
+
// Remove debug tools from window
|
|
58
|
+
delete window.VanillaForgeDebug;
|
|
59
|
+
|
|
60
|
+
this.logger.info('Framework debug mode disabled');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get component manager statistics
|
|
65
|
+
*/
|
|
66
|
+
getComponentStats() {
|
|
67
|
+
if (!this.app.componentManager) {
|
|
68
|
+
return { error: 'Component manager not available' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const activeComponents = this.app.componentManager.getActiveComponents();
|
|
72
|
+
const registeredComponents = this.app.componentManager.getRegisteredComponents();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
registeredComponents: registeredComponents,
|
|
76
|
+
activeComponents: Array.from(activeComponents.keys()),
|
|
77
|
+
totalRegistered: registeredComponents.length,
|
|
78
|
+
totalActive: activeComponents.size,
|
|
79
|
+
components: Array.from(activeComponents.entries()).map(([id, instance]) => ({
|
|
80
|
+
id,
|
|
81
|
+
name: instance.name,
|
|
82
|
+
isInitialized: instance.isInitialized,
|
|
83
|
+
isRendered: instance.isRendered,
|
|
84
|
+
isDestroyed: instance.isDestroyed,
|
|
85
|
+
state: instance.state,
|
|
86
|
+
props: instance.props
|
|
87
|
+
}))
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get event bus statistics
|
|
93
|
+
*/
|
|
94
|
+
getEventBusStats() {
|
|
95
|
+
if (!this.app.eventBus) {
|
|
96
|
+
return { error: 'Event bus not available' };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
...this.app.eventBus.getStats(),
|
|
101
|
+
recentEvents: this.app.eventBus.getHistory(20)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get router information
|
|
107
|
+
*/
|
|
108
|
+
getRouterInfo() {
|
|
109
|
+
if (!this.app.router) {
|
|
110
|
+
return { error: 'Router not available' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
currentRoute: this.app.router.getCurrentRoute(),
|
|
115
|
+
isNavigating: this.app.router.isNavigating,
|
|
116
|
+
targetPath: this.app.router.targetPath
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get error handler statistics
|
|
122
|
+
*/
|
|
123
|
+
getErrorStats() {
|
|
124
|
+
if (!this.app.errorHandler) {
|
|
125
|
+
return { error: 'Error handler not available' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return this.app.errorHandler.getErrorStats();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get performance information
|
|
133
|
+
*/
|
|
134
|
+
getPerformanceInfo() {
|
|
135
|
+
const navigation = performance.getEntriesByType('navigation')[0];
|
|
136
|
+
const marks = performance.getEntriesByType('mark');
|
|
137
|
+
const measures = performance.getEntriesByType('measure');
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
navigation: navigation ? {
|
|
141
|
+
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
|
|
142
|
+
load: navigation.loadEventEnd - navigation.loadEventStart,
|
|
143
|
+
domComplete: navigation.domComplete - navigation.fetchStart,
|
|
144
|
+
totalLoadTime: navigation.loadEventEnd - navigation.fetchStart
|
|
145
|
+
} : null,
|
|
146
|
+
marks: marks.map(mark => ({
|
|
147
|
+
name: mark.name,
|
|
148
|
+
startTime: mark.startTime
|
|
149
|
+
})),
|
|
150
|
+
measures: measures.map(measure => ({
|
|
151
|
+
name: measure.name,
|
|
152
|
+
duration: measure.duration,
|
|
153
|
+
startTime: measure.startTime
|
|
154
|
+
})),
|
|
155
|
+
memory: performance.memory ? {
|
|
156
|
+
usedJSHeapSize: performance.memory.usedJSHeapSize,
|
|
157
|
+
totalJSHeapSize: performance.memory.totalJSHeapSize,
|
|
158
|
+
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
|
|
159
|
+
} : null
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Mark performance milestone
|
|
165
|
+
*/
|
|
166
|
+
markPerformance(name) {
|
|
167
|
+
try {
|
|
168
|
+
performance.mark(name);
|
|
169
|
+
this.performanceMarks.set(name, performance.now());
|
|
170
|
+
this.logger.debug(`Performance mark: ${name}`);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.logger.warn(`Failed to mark performance: ${name}`, error);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Measure performance between two marks
|
|
178
|
+
*/
|
|
179
|
+
measurePerformance(name, startMark, endMark) {
|
|
180
|
+
try {
|
|
181
|
+
performance.measure(name, startMark, endMark);
|
|
182
|
+
this.logger.debug(`Performance measure: ${name}`);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
this.logger.warn(`Failed to measure performance: ${name}`, error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Log detailed framework state
|
|
189
|
+
*/
|
|
190
|
+
logFrameworkState() {
|
|
191
|
+
const stats = {
|
|
192
|
+
components: this.getComponentStats(),
|
|
193
|
+
eventBus: this.getEventBusStats(),
|
|
194
|
+
router: this.getRouterInfo(),
|
|
195
|
+
errors: this.getErrorStats(),
|
|
196
|
+
performance: this.getPerformanceInfo()
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
console.group('VanillaForge Framework State');
|
|
200
|
+
console.table(stats);
|
|
201
|
+
console.groupEnd();
|
|
202
|
+
|
|
203
|
+
return stats;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Simulate component stress test
|
|
207
|
+
*/ async stressTestComponents(iterations = 10, componentName = null) {
|
|
208
|
+
if (!this.isEnabled) {
|
|
209
|
+
console.warn('Debug mode must be enabled for stress testing');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Auto-detect a registered component if none specified
|
|
214
|
+
if (!componentName) {
|
|
215
|
+
const registeredComponents = this.app.componentManager?.getRegisteredComponents() || [];
|
|
216
|
+
if (registeredComponents.length === 0) {
|
|
217
|
+
console.warn('No registered components found for stress testing');
|
|
218
|
+
return { error: 'No components registered' };
|
|
219
|
+
}
|
|
220
|
+
componentName = registeredComponents[0]; // Use first registered component
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.logger.info(`Starting component stress test (${iterations} iterations) on component: ${componentName}`);
|
|
224
|
+
|
|
225
|
+
const startTime = performance.now();
|
|
226
|
+
const results = {
|
|
227
|
+
iterations,
|
|
228
|
+
componentName,
|
|
229
|
+
successes: 0,
|
|
230
|
+
failures: 0,
|
|
231
|
+
errors: []
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < iterations; i++) {
|
|
235
|
+
try {
|
|
236
|
+
// Check if component is registered before trying to load it
|
|
237
|
+
if (!this.app.componentManager.components.has(componentName)) {
|
|
238
|
+
throw new Error(`Component '${componentName}' is not registered`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Simulate rapid component loading/unloading
|
|
242
|
+
await this.app.componentManager.loadComponent(componentName, {}, 'main-content');
|
|
243
|
+
await new Promise(resolve => setTimeout(resolve, 10)); // Small delay
|
|
244
|
+
|
|
245
|
+
results.successes++;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
results.failures++;
|
|
248
|
+
results.errors.push(error.message);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const endTime = performance.now();
|
|
253
|
+
results.duration = endTime - startTime;
|
|
254
|
+
results.averageTime = results.duration / iterations;
|
|
255
|
+
|
|
256
|
+
console.log('Stress test results:', results);
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Test event bus performance
|
|
262
|
+
*/ testEventBusPerformance(eventCount = 1000) {
|
|
263
|
+
if (!this.isEnabled) {
|
|
264
|
+
console.warn('Debug mode must be enabled for performance testing');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.logger.info(`Testing event bus performance (${eventCount} events)`);
|
|
269
|
+
|
|
270
|
+
const startTime = performance.now();
|
|
271
|
+
|
|
272
|
+
// Create multiple listeners
|
|
273
|
+
const listeners = [];
|
|
274
|
+
for (let i = 0; i < 10; i++) {
|
|
275
|
+
const unsubscribe = this.app.eventBus.on(`test-event-${i}`, () => {});
|
|
276
|
+
listeners.push(unsubscribe);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Emit events
|
|
280
|
+
for (let i = 0; i < eventCount; i++) {
|
|
281
|
+
this.app.eventBus.emit(`test-event-${i % 10}`, { data: i });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const endTime = performance.now();
|
|
285
|
+
|
|
286
|
+
// Clean up listeners
|
|
287
|
+
listeners.forEach(unsubscribe => unsubscribe());
|
|
288
|
+
|
|
289
|
+
const results = {
|
|
290
|
+
eventCount,
|
|
291
|
+
duration: endTime - startTime,
|
|
292
|
+
eventsPerSecond: eventCount / ((endTime - startTime) / 1000)
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
console.log('Event bus performance results:', results);
|
|
296
|
+
return results;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Monitor memory usage over time
|
|
301
|
+
*/ startMemoryMonitoring(interval = 5000) {
|
|
302
|
+
if (!this.isEnabled) {
|
|
303
|
+
console.warn('Debug mode must be enabled for memory monitoring');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (this.memoryMonitor) {
|
|
308
|
+
clearInterval(this.memoryMonitor);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this.logger.info(`Starting memory monitoring (${interval}ms interval)`);
|
|
312
|
+
|
|
313
|
+
this.memoryMonitor = setInterval(() => {
|
|
314
|
+
const memory = this.getPerformanceInfo().memory;
|
|
315
|
+
if (memory) {
|
|
316
|
+
this.logger.debug('Memory usage:', {
|
|
317
|
+
used: Math.round(memory.usedJSHeapSize / 1024 / 1024) + ' MB',
|
|
318
|
+
total: Math.round(memory.totalJSHeapSize / 1024 / 1024) + ' MB',
|
|
319
|
+
limit: Math.round(memory.jsHeapSizeLimit / 1024 / 1024) + ' MB'
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}, interval);
|
|
323
|
+
|
|
324
|
+
return this.memoryMonitor;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Stop memory monitoring
|
|
329
|
+
*/
|
|
330
|
+
stopMemoryMonitoring() {
|
|
331
|
+
if (this.memoryMonitor) {
|
|
332
|
+
clearInterval(this.memoryMonitor);
|
|
333
|
+
this.memoryMonitor = null;
|
|
334
|
+
this.logger.info('Memory monitoring stopped');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get framework health check
|
|
340
|
+
*/ healthCheck() {
|
|
341
|
+
const health = {
|
|
342
|
+
timestamp: new Date().toISOString(),
|
|
343
|
+
framework: {
|
|
344
|
+
initialized: !!this.app.isInitialized,
|
|
345
|
+
version: '1.0.0'
|
|
346
|
+
},
|
|
347
|
+
components: {
|
|
348
|
+
managerInitialized: !!this.app.componentManager?.isInitialized,
|
|
349
|
+
activeCount: this.app.componentManager?.getActiveComponents().size || 0
|
|
350
|
+
},
|
|
351
|
+
eventBus: {
|
|
352
|
+
available: !!this.app.eventBus,
|
|
353
|
+
stats: this.app.eventBus ? this.app.eventBus.getStats() : null
|
|
354
|
+
},
|
|
355
|
+
router: {
|
|
356
|
+
initialized: !!this.app.router?.isInitialized,
|
|
357
|
+
currentRoute: this.app.router?.getCurrentRoute()?.name || null
|
|
358
|
+
},
|
|
359
|
+
memory: this.getPerformanceInfo().memory,
|
|
360
|
+
errors: this.getErrorStats()
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Only log to console in development mode
|
|
364
|
+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
365
|
+
console.log('Framework Health Check:', health);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return health;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Auto-expose in development
|
|
373
|
+
if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
|
|
374
|
+
window.VanillaForgeDebug = FrameworkDebug;
|
|
375
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility
|
|
3
|
+
*
|
|
4
|
+
* Provides structured logging functionality with different log levels,
|
|
5
|
+
* formatting, and optional remote logging capabilities.
|
|
6
|
+
*
|
|
7
|
+
* @author VanillaForge Team
|
|
8
|
+
* @version 3.0.0
|
|
9
|
+
* @since 2025-06-14
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { LocalStorageAdapter } from './storage.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Log levels enum
|
|
16
|
+
* @readonly
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
export const LogLevel = {
|
|
20
|
+
DEBUG: 'debug',
|
|
21
|
+
INFO: 'info',
|
|
22
|
+
WARN: 'warn',
|
|
23
|
+
ERROR: 'error'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Logger class for structured application logging
|
|
28
|
+
*
|
|
29
|
+
* Provides different log levels, formatting, and contextual information.
|
|
30
|
+
* In production, logs can be sent to external logging services.
|
|
31
|
+
*/
|
|
32
|
+
export class Logger {
|
|
33
|
+
/**
|
|
34
|
+
* Create a new Logger instance
|
|
35
|
+
*
|
|
36
|
+
* @param {string} context - The context/module name for this logger
|
|
37
|
+
* @param {string} [level=LogLevel.INFO] - Minimum log level to output
|
|
38
|
+
*/
|
|
39
|
+
constructor(context = 'App', level = LogLevel.INFO, storageAdapter) {
|
|
40
|
+
this.context = context;
|
|
41
|
+
this.level = level;
|
|
42
|
+
this.startTime = Date.now();
|
|
43
|
+
this.storage = storageAdapter || (typeof window !== 'undefined' ? new LocalStorageAdapter() : null);
|
|
44
|
+
|
|
45
|
+
this.levelPriorities = {
|
|
46
|
+
[LogLevel.DEBUG]: 0,
|
|
47
|
+
[LogLevel.INFO]: 1,
|
|
48
|
+
[LogLevel.WARN]: 2,
|
|
49
|
+
[LogLevel.ERROR]: 3
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
this.consoleMethods = {
|
|
53
|
+
[LogLevel.DEBUG]: 'debug',
|
|
54
|
+
[LogLevel.INFO]: 'log',
|
|
55
|
+
[LogLevel.WARN]: 'warn',
|
|
56
|
+
[LogLevel.ERROR]: 'error'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
this.colors = {
|
|
60
|
+
[LogLevel.DEBUG]: '#6b7280',
|
|
61
|
+
[LogLevel.INFO]: '#2563eb',
|
|
62
|
+
[LogLevel.WARN]: '#d97706',
|
|
63
|
+
[LogLevel.ERROR]: '#dc2626'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if a log level should be output
|
|
69
|
+
*
|
|
70
|
+
* @private
|
|
71
|
+
* @param {string} level - Log level to check
|
|
72
|
+
* @returns {boolean} True if level should be logged
|
|
73
|
+
*/
|
|
74
|
+
shouldLog(level) {
|
|
75
|
+
return this.levelPriorities[level] >= this.levelPriorities[this.level];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format log message with timestamp and context
|
|
80
|
+
*
|
|
81
|
+
* @private
|
|
82
|
+
* @param {string} level - Log level
|
|
83
|
+
* @param {string} message - Log message
|
|
84
|
+
* @param {Object} [data] - Additional data to log
|
|
85
|
+
* @returns {Object} Formatted log entry
|
|
86
|
+
*/
|
|
87
|
+
formatMessage(level, message, data = {}) {
|
|
88
|
+
const timestamp = new Date().toISOString();
|
|
89
|
+
const uptime = Date.now() - this.startTime;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
timestamp,
|
|
93
|
+
uptime: `${uptime}ms`,
|
|
94
|
+
level: level.toUpperCase(),
|
|
95
|
+
context: this.context,
|
|
96
|
+
message,
|
|
97
|
+
data: Object.keys(data).length > 0 ? data : undefined,
|
|
98
|
+
userAgent: navigator.userAgent,
|
|
99
|
+
url: window.location.href
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Output log to console with styling
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
* @param {string} level - Log level
|
|
108
|
+
* @param {Object} logEntry - Formatted log entry
|
|
109
|
+
*/
|
|
110
|
+
outputToConsole(level, logEntry) {
|
|
111
|
+
const consoleMethod = this.consoleMethods[level];
|
|
112
|
+
const color = this.colors[level];
|
|
113
|
+
|
|
114
|
+
// Create styled output for development
|
|
115
|
+
const prefix = `%c[${logEntry.level}] ${logEntry.context}`;
|
|
116
|
+
const style = `color: ${color}; font-weight: bold;`;
|
|
117
|
+
|
|
118
|
+
if (logEntry.data) {
|
|
119
|
+
console[consoleMethod](prefix, style, logEntry.message, logEntry.data); } else {
|
|
120
|
+
console[consoleMethod](prefix, style, logEntry.message);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Only log full structured data in development mode
|
|
124
|
+
if (level === LogLevel.DEBUG && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
|
|
125
|
+
console.debug('Full log entry:', logEntry);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Send log to remote logging service (if configured)
|
|
131
|
+
*
|
|
132
|
+
* @private
|
|
133
|
+
* @param {Object} logEntry - Formatted log entry
|
|
134
|
+
*/
|
|
135
|
+
async sendToRemoteLogging(logEntry) {
|
|
136
|
+
if (!this.storage || (logEntry.level !== 'WARN' && logEntry.level !== 'ERROR')) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const logs = this.storage.getLogs('ucm_logs');
|
|
142
|
+
logs.push(logEntry);
|
|
143
|
+
this.storage.saveLogs('ucm_logs', logs);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('Failed to send log to remote service:', error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Core logging method
|
|
151
|
+
*
|
|
152
|
+
* @private
|
|
153
|
+
* @param {string} level - Log level
|
|
154
|
+
* @param {string} message - Log message
|
|
155
|
+
* @param {Object} [data] - Additional data to log
|
|
156
|
+
*/
|
|
157
|
+
log(level, message, data = {}) {
|
|
158
|
+
if (!this.shouldLog(level)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const logEntry = this.formatMessage(level, message, data);
|
|
163
|
+
|
|
164
|
+
// Output to console
|
|
165
|
+
this.outputToConsole(level, logEntry);
|
|
166
|
+
|
|
167
|
+
// Send to remote logging (async, don't wait)
|
|
168
|
+
this.sendToRemoteLogging(logEntry);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Log debug message
|
|
173
|
+
*
|
|
174
|
+
* @param {string} message - Debug message
|
|
175
|
+
* @param {Object} [data] - Additional debug data
|
|
176
|
+
*/
|
|
177
|
+
debug(message, data = {}) {
|
|
178
|
+
this.log(LogLevel.DEBUG, message, data);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Log info message
|
|
183
|
+
*
|
|
184
|
+
* @param {string} message - Info message
|
|
185
|
+
* @param {Object} [data] - Additional info data
|
|
186
|
+
*/
|
|
187
|
+
info(message, data = {}) {
|
|
188
|
+
this.log(LogLevel.INFO, message, data);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Log warning message
|
|
193
|
+
*
|
|
194
|
+
* @param {string} message - Warning message
|
|
195
|
+
* @param {Object} [data] - Additional warning data
|
|
196
|
+
*/
|
|
197
|
+
warn(message, data = {}) {
|
|
198
|
+
this.log(LogLevel.WARN, message, data);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Log error message
|
|
203
|
+
*
|
|
204
|
+
* @param {string} message - Error message
|
|
205
|
+
* @param {Error|Object} [data] - Error object or additional error data
|
|
206
|
+
*/
|
|
207
|
+
error(message, data = {}) {
|
|
208
|
+
// If data is an Error object, extract useful information
|
|
209
|
+
if (data instanceof Error) {
|
|
210
|
+
data = {
|
|
211
|
+
name: data.name,
|
|
212
|
+
message: data.message,
|
|
213
|
+
stack: data.stack,
|
|
214
|
+
cause: data.cause
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.log(LogLevel.ERROR, message, data);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Create a timer for performance measurement
|
|
223
|
+
*
|
|
224
|
+
* @param {string} label - Timer label
|
|
225
|
+
* @returns {Function} Function to call to end the timer
|
|
226
|
+
*/
|
|
227
|
+
timer(label) {
|
|
228
|
+
const startTime = performance.now();
|
|
229
|
+
this.debug(`Timer started: ${label}`);
|
|
230
|
+
|
|
231
|
+
return () => {
|
|
232
|
+
const endTime = performance.now();
|
|
233
|
+
const duration = endTime - startTime;
|
|
234
|
+
this.info(`Timer completed: ${label}`, {
|
|
235
|
+
duration: `${duration.toFixed(2)}ms`
|
|
236
|
+
});
|
|
237
|
+
return duration;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Log method execution time
|
|
243
|
+
*
|
|
244
|
+
* @param {string} methodName - Name of the method being timed
|
|
245
|
+
* @param {Function} fn - Function to execute and time
|
|
246
|
+
* @param {...any} args - Arguments to pass to the function
|
|
247
|
+
* @returns {any} Result of the function execution
|
|
248
|
+
*/
|
|
249
|
+
async timeMethod(methodName, fn, ...args) {
|
|
250
|
+
const endTimer = this.timer(methodName);
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const result = await fn(...args);
|
|
254
|
+
endTimer();
|
|
255
|
+
return result;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
endTimer();
|
|
258
|
+
this.error(`Method ${methodName} failed`, error);
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Set the minimum log level
|
|
265
|
+
*
|
|
266
|
+
* @param {string} level - New minimum log level
|
|
267
|
+
*/
|
|
268
|
+
setLevel(level) {
|
|
269
|
+
if (Object.prototype.hasOwnProperty.call(this.levelPriorities, level)) {
|
|
270
|
+
this.level = level;
|
|
271
|
+
this.info(`Log level changed to: ${level}`);
|
|
272
|
+
} else {
|
|
273
|
+
this.warn(`Invalid log level: ${level}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Create a child logger with additional context
|
|
279
|
+
*
|
|
280
|
+
* @param {string} childContext - Additional context for the child logger
|
|
281
|
+
* @returns {Logger} New logger instance
|
|
282
|
+
*/
|
|
283
|
+
child(childContext) {
|
|
284
|
+
const fullContext = `${this.context}:${childContext}`;
|
|
285
|
+
return new Logger(fullContext, this.level, this.storage);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
getLogs() {
|
|
289
|
+
return this.storage ? this.storage.getLogs('ucm_logs') : [];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
clearLogs() {
|
|
293
|
+
if (this.storage) {
|
|
294
|
+
this.storage.removeItem('ucm_logs');
|
|
295
|
+
this.info('Logs cleared');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
exportLogs() {
|
|
300
|
+
if (typeof window === 'undefined' || !this.storage) {
|
|
301
|
+
this.warn('Log export is only available in a browser environment with storage.');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const logs = this.getLogs();
|
|
307
|
+
const logData = JSON.stringify(logs, null, 2);
|
|
308
|
+
const blob = new Blob([logData], { type: 'application/json' });
|
|
309
|
+
const url = URL.createObjectURL(blob);
|
|
310
|
+
|
|
311
|
+
const a = document.createElement('a');
|
|
312
|
+
a.href = url;
|
|
313
|
+
a.download = `ucm-logs-${new Date().toISOString().split('T')[0]}.json`;
|
|
314
|
+
document.body.appendChild(a);
|
|
315
|
+
a.click();
|
|
316
|
+
document.body.removeChild(a);
|
|
317
|
+
URL.revokeObjectURL(url);
|
|
318
|
+
|
|
319
|
+
this.info('Logs exported successfully');
|
|
320
|
+
} catch (error) {
|
|
321
|
+
this.error('Failed to export logs', error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|