- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
223 lines
7.6 KiB
JavaScript
223 lines
7.6 KiB
JavaScript
/**
|
|
* Error Boundary System für Module
|
|
* Bietet Schutz vor Module-Crashes und Recovery-Mechanismen
|
|
*/
|
|
import { Logger } from './logger.js';
|
|
|
|
export class ModuleErrorBoundary {
|
|
constructor() {
|
|
this.crashedModules = new Set();
|
|
this.recoveryAttempts = new Map();
|
|
this.maxRecoveryAttempts = 3;
|
|
this.recoveryDelay = 1000; // 1 second
|
|
}
|
|
|
|
/**
|
|
* Wraps a module with error handling
|
|
* @param {Object} module - The module to wrap
|
|
* @param {string} moduleName - Name of the module for logging
|
|
* @returns {Proxy} - Protected module instance
|
|
*/
|
|
wrapModule(module, moduleName) {
|
|
if (!module || typeof module !== 'object') {
|
|
Logger.warn(`[ErrorBoundary] Cannot wrap non-object module: ${moduleName}`);
|
|
return module;
|
|
}
|
|
|
|
return new Proxy(module, {
|
|
get: (target, prop, receiver) => {
|
|
const originalValue = target[prop];
|
|
|
|
// If property is not a function, return as-is
|
|
if (typeof originalValue !== 'function') {
|
|
return originalValue;
|
|
}
|
|
|
|
// Check if property is non-configurable - return original if so
|
|
const descriptor = Object.getOwnPropertyDescriptor(target, prop);
|
|
if (descriptor && !descriptor.configurable) {
|
|
return originalValue;
|
|
}
|
|
|
|
// Return wrapped function with error handling
|
|
return (...args) => {
|
|
try {
|
|
const result = originalValue.apply(target, args);
|
|
|
|
// Handle async functions
|
|
if (result && typeof result.catch === 'function') {
|
|
return result.catch(error => {
|
|
this.handleModuleError(error, moduleName, prop, args);
|
|
return this.getRecoveryValue(moduleName, prop);
|
|
});
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
this.handleModuleError(error, moduleName, prop, args);
|
|
return this.getRecoveryValue(moduleName, prop);
|
|
}
|
|
};
|
|
},
|
|
getOwnPropertyDescriptor: (target, prop) => {
|
|
const descriptor = Object.getOwnPropertyDescriptor(target, prop);
|
|
|
|
// For non-configurable properties, return original descriptor
|
|
if (descriptor && !descriptor.configurable) {
|
|
return descriptor;
|
|
}
|
|
|
|
return descriptor;
|
|
},
|
|
has: (target, prop) => {
|
|
return prop in target;
|
|
},
|
|
ownKeys: (target) => {
|
|
return Object.getOwnPropertyNames(target);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handles module errors with recovery logic
|
|
* @param {Error} error - The error that occurred
|
|
* @param {string} moduleName - Name of the failing module
|
|
* @param {string} method - Method that failed
|
|
* @param {Array} args - Arguments passed to the method
|
|
*/
|
|
handleModuleError(error, moduleName, method, args) {
|
|
const errorKey = `${moduleName}.${method}`;
|
|
|
|
Logger.error(`[ErrorBoundary] Module ${moduleName} crashed in ${method}():`, error);
|
|
|
|
// Track crashed modules
|
|
this.crashedModules.add(moduleName);
|
|
|
|
// Track recovery attempts
|
|
const attempts = this.recoveryAttempts.get(errorKey) || 0;
|
|
this.recoveryAttempts.set(errorKey, attempts + 1);
|
|
|
|
// Emit custom event for external handling
|
|
window.dispatchEvent(new CustomEvent('module-error', {
|
|
detail: {
|
|
moduleName,
|
|
method,
|
|
error: error.message,
|
|
args,
|
|
attempts: attempts + 1
|
|
}
|
|
}));
|
|
|
|
// Attempt recovery if under limit
|
|
if (attempts < this.maxRecoveryAttempts) {
|
|
this.scheduleRecovery(moduleName, method, args);
|
|
} else {
|
|
Logger.error(`[ErrorBoundary] Module ${moduleName} exceeded recovery attempts. Marking as permanently failed.`);
|
|
this.markModuleAsPermanentlyFailed(moduleName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedules a recovery attempt for a failed module method
|
|
* @param {string} moduleName - Name of the module
|
|
* @param {string} method - Method to retry
|
|
* @param {Array} args - Original arguments
|
|
*/
|
|
scheduleRecovery(moduleName, method, args) {
|
|
setTimeout(() => {
|
|
try {
|
|
Logger.info(`[ErrorBoundary] Attempting recovery for ${moduleName}.${method}()`);
|
|
// This would need module registry integration
|
|
// For now, just log the attempt
|
|
} catch (recoveryError) {
|
|
Logger.error(`[ErrorBoundary] Recovery failed for ${moduleName}.${method}():`, recoveryError);
|
|
}
|
|
}, this.recoveryDelay);
|
|
}
|
|
|
|
/**
|
|
* Returns a safe fallback value for failed module methods
|
|
* @param {string} moduleName - Name of the module
|
|
* @param {string} method - Method that failed
|
|
* @returns {*} - Safe fallback value
|
|
*/
|
|
getRecoveryValue(moduleName, method) {
|
|
// Return safe defaults based on common method names
|
|
switch (method) {
|
|
case 'init':
|
|
case 'destroy':
|
|
case 'update':
|
|
case 'render':
|
|
return Promise.resolve();
|
|
case 'getData':
|
|
case 'getConfig':
|
|
return {};
|
|
case 'isEnabled':
|
|
case 'isActive':
|
|
return false;
|
|
default:
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Marks a module as permanently failed
|
|
* @param {string} moduleName - Name of the module
|
|
*/
|
|
markModuleAsPermanentlyFailed(moduleName) {
|
|
window.dispatchEvent(new CustomEvent('module-permanent-failure', {
|
|
detail: { moduleName }
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Gets health status of all modules
|
|
* @returns {Object} - Health status report
|
|
*/
|
|
getHealthStatus() {
|
|
return {
|
|
totalCrashedModules: this.crashedModules.size,
|
|
crashedModules: Array.from(this.crashedModules),
|
|
recoveryAttempts: Object.fromEntries(this.recoveryAttempts),
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Resets error tracking for a module (useful for hot reload)
|
|
* @param {string} moduleName - Name of the module to reset
|
|
*/
|
|
resetModule(moduleName) {
|
|
this.crashedModules.delete(moduleName);
|
|
|
|
// Remove all recovery attempts for this module
|
|
for (const [key] of this.recoveryAttempts) {
|
|
if (key.startsWith(`${moduleName}.`)) {
|
|
this.recoveryAttempts.delete(key);
|
|
}
|
|
}
|
|
|
|
Logger.info(`[ErrorBoundary] Reset error tracking for module: ${moduleName}`);
|
|
}
|
|
|
|
/**
|
|
* Clears all error tracking
|
|
*/
|
|
reset() {
|
|
this.crashedModules.clear();
|
|
this.recoveryAttempts.clear();
|
|
Logger.info('[ErrorBoundary] Reset all error tracking');
|
|
}
|
|
}
|
|
|
|
// Global instance
|
|
export const moduleErrorBoundary = new ModuleErrorBoundary();
|
|
|
|
// Global error handlers
|
|
window.addEventListener('error', (event) => {
|
|
Logger.error('[Global] Unhandled error:', event.error || event.message);
|
|
});
|
|
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
Logger.error('[Global] Unhandled promise rejection:', event.reason);
|
|
}); |