UNPKG

superaugment

Version:

Enterprise-grade MCP server with world-class C++ analysis, robust error handling, and production-ready architecture for VS Code Augment

299 lines 10.5 kB
/** * SuperAugment Error Handler * * Provides centralized error handling, logging, and recovery mechanisms * for the SuperAugment MCP server. */ import { logger } from '../utils/logger.js'; import { SuperAugmentError, ErrorCode, ErrorSeverity, isSuperAugmentError, wrapError, } from './ErrorTypes.js'; /** * Default error handler configuration */ const DEFAULT_CONFIG = { enableRetry: true, maxRetryAttempts: 3, retryDelayMs: 1000, enableCircuitBreaker: true, circuitBreakerThreshold: 5, enableMetrics: true, }; /** * Default retry strategy with exponential backoff */ class ExponentialBackoffRetryStrategy { maxAttempts; baseDelayMs; maxDelayMs; constructor(maxAttempts = 3, baseDelayMs = 1000, maxDelayMs = 10000) { this.maxAttempts = maxAttempts; this.baseDelayMs = baseDelayMs; this.maxDelayMs = maxDelayMs; } shouldRetry(error, attempt) { return error.isRetryable && attempt < this.maxAttempts; } getDelay(attempt) { const delay = this.baseDelayMs * Math.pow(2, attempt - 1); return Math.min(delay, this.maxDelayMs); } } /** * Centralized error handler for SuperAugment */ export class ErrorHandler { config; metrics; retryStrategy; constructor(config = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; this.metrics = { totalErrors: 0, errorsByCode: new Map(), errorsBySeverity: new Map(), errorsByTool: new Map(), recentErrors: [], circuitBreakerState: 'closed', consecutiveFailures: 0, }; this.retryStrategy = new ExponentialBackoffRetryStrategy(this.config.maxRetryAttempts, this.config.retryDelayMs); } /** * Handle an error with full processing pipeline */ async handleError(error, context = {}, operation) { const superAugmentError = this.normalizeError(error, context); // Update metrics this.updateMetrics(superAugmentError); // Log the error this.logError(superAugmentError); // Check circuit breaker if (this.isCircuitBreakerOpen()) { throw new SuperAugmentError('Service temporarily unavailable due to repeated failures', ErrorCode.RESOURCE_EXHAUSTED, ErrorSeverity.HIGH, context); } // Attempt retry if applicable and operation is provided if (operation && this.shouldRetry(superAugmentError)) { return this.executeWithRetry(operation, superAugmentError, context); } // Update circuit breaker state this.updateCircuitBreaker(superAugmentError); throw superAugmentError; } /** * Execute operation with retry logic */ async executeWithRetry(operation, originalError, context) { let lastError = originalError; for (let attempt = 1; attempt <= this.config.maxRetryAttempts; attempt++) { if (!this.retryStrategy.shouldRetry(lastError, attempt)) { break; } // Wait before retry const delay = this.retryStrategy.getDelay(attempt); await this.sleep(delay); try { logger.info(`Retrying operation, attempt ${attempt}/${this.config.maxRetryAttempts}`, { toolName: context.toolName, originalError: originalError.code, delay, }); const result = await operation(); // Success - reset circuit breaker this.resetCircuitBreaker(); logger.info(`Operation succeeded on retry attempt ${attempt}`, { toolName: context.toolName, }); return result; } catch (error) { lastError = this.normalizeError(error, context); this.updateMetrics(lastError); this.logError(lastError, `Retry attempt ${attempt} failed`); } } // All retries failed this.updateCircuitBreaker(lastError); throw lastError; } /** * Normalize any error to SuperAugmentError */ normalizeError(error, context) { if (isSuperAugmentError(error)) { // Merge additional context return new SuperAugmentError(error.message, error.code, error.severity, { ...error.context, ...context }, error.isRetryable, error.originalError); } return wrapError(error, undefined, ErrorCode.UNKNOWN_ERROR, context); } /** * Log error with appropriate level based on severity */ logError(error, additionalMessage) { const logData = { ...error.toJSON(), additionalMessage, }; switch (error.severity) { case ErrorSeverity.CRITICAL: logger.error('Critical error occurred', logData); break; case ErrorSeverity.HIGH: logger.error('High severity error occurred', logData); break; case ErrorSeverity.MEDIUM: logger.warn('Medium severity error occurred', logData); break; case ErrorSeverity.LOW: logger.info('Low severity error occurred', logData); break; default: logger.warn('Unknown severity error occurred', logData); } } /** * Update error metrics */ updateMetrics(error) { if (!this.config.enableMetrics) { return; } this.metrics.totalErrors++; // Update error count by code const codeCount = this.metrics.errorsByCode.get(error.code) || 0; this.metrics.errorsByCode.set(error.code, codeCount + 1); // Update error count by severity const severityCount = this.metrics.errorsBySeverity.get(error.severity) || 0; this.metrics.errorsBySeverity.set(error.severity, severityCount + 1); // Update error count by tool if (error.context.toolName) { const toolCount = this.metrics.errorsByTool.get(error.context.toolName) || 0; this.metrics.errorsByTool.set(error.context.toolName, toolCount + 1); } // Keep recent errors (last 100) this.metrics.recentErrors.push(error); if (this.metrics.recentErrors.length > 100) { this.metrics.recentErrors.shift(); } } /** * Check if circuit breaker should be open */ isCircuitBreakerOpen() { if (!this.config.enableCircuitBreaker) { return false; } return this.metrics.circuitBreakerState === 'open'; } /** * Update circuit breaker state based on error */ updateCircuitBreaker(error) { if (!this.config.enableCircuitBreaker) { return; } if (error.severity === ErrorSeverity.CRITICAL || error.severity === ErrorSeverity.HIGH) { this.metrics.consecutiveFailures++; this.metrics.lastFailureTime = new Date(); if (this.metrics.consecutiveFailures >= this.config.circuitBreakerThreshold) { this.metrics.circuitBreakerState = 'open'; logger.warn('Circuit breaker opened due to consecutive failures', { consecutiveFailures: this.metrics.consecutiveFailures, threshold: this.config.circuitBreakerThreshold, }); } } } /** * Reset circuit breaker after successful operation */ resetCircuitBreaker() { if (this.metrics.circuitBreakerState !== 'closed') { this.metrics.circuitBreakerState = 'closed'; this.metrics.consecutiveFailures = 0; logger.info('Circuit breaker reset after successful operation'); } } /** * Check if error should be retried */ shouldRetry(error) { return this.config.enableRetry && error.isRetryable; } /** * Sleep utility for retry delays */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Get current error metrics */ getMetrics() { return { ...this.metrics }; } /** * Reset error metrics */ resetMetrics() { this.metrics = { totalErrors: 0, errorsByCode: new Map(), errorsBySeverity: new Map(), errorsByTool: new Map(), recentErrors: [], circuitBreakerState: 'closed', consecutiveFailures: 0, }; } /** * Get error statistics for monitoring */ getErrorStatistics() { return { totalErrors: this.metrics.totalErrors, errorsByCode: Object.fromEntries(this.metrics.errorsByCode), errorsBySeverity: Object.fromEntries(this.metrics.errorsBySeverity), errorsByTool: Object.fromEntries(this.metrics.errorsByTool), circuitBreakerState: this.metrics.circuitBreakerState, consecutiveFailures: this.metrics.consecutiveFailures, recentErrorsCount: this.metrics.recentErrors.length, lastFailureTime: this.metrics.lastFailureTime, }; } } /** * Global error handler instance */ export const globalErrorHandler = new ErrorHandler(); /** * Utility function to handle errors in async operations */ export async function handleAsyncError(operation, context = {}) { try { return await operation(); } catch (error) { await globalErrorHandler.handleError(error, context, operation); throw error; // This line should never be reached due to handleError throwing } } /** * Decorator for automatic error handling in class methods */ export function HandleErrors(context = {}) { return function (_target, _propertyName, descriptor) { const method = descriptor.value; descriptor.value = async function (...args) { try { return await method.apply(this, args); } catch (error) { const errorContext = { ...context, toolName: context.toolName || this.name || this.constructor.name, }; await globalErrorHandler.handleError(error, errorContext); } }; }; } //# sourceMappingURL=ErrorHandler.js.map