UNPKG

defect-inspection-tools-mcp-server

Version:

Defect inspection tools server handling defect detection, analysis, and reporting with AI/ML capabilities for quality control

279 lines 11.1 kB
import { logger } from '../utils/logger.js'; import { ErrorHandler } from '../middleware/error-handler.js'; export var CircuitBreakerState; (function (CircuitBreakerState) { CircuitBreakerState["CLOSED"] = "CLOSED"; CircuitBreakerState["OPEN"] = "OPEN"; CircuitBreakerState["HALF_OPEN"] = "HALF_OPEN"; })(CircuitBreakerState || (CircuitBreakerState = {})); export var ServiceHealthStatus; (function (ServiceHealthStatus) { ServiceHealthStatus["HEALTHY"] = "HEALTHY"; ServiceHealthStatus["DEGRADED"] = "DEGRADED"; ServiceHealthStatus["UNHEALTHY"] = "UNHEALTHY"; })(ServiceHealthStatus || (ServiceHealthStatus = {})); export class BaseService { constructor(serviceName, config = {}) { this.responseTimes = []; this.serviceName = serviceName; this.config = { timeout: config.timeout || 30000, retries: config.retries || 3, retryDelay: config.retryDelay || 1000, circuitBreakerThreshold: config.circuitBreakerThreshold || 5, circuitBreakerTimeout: config.circuitBreakerTimeout || 60000 }; this.metrics = { totalRequests: 0, successCount: 0, errorCount: 0, averageResponseTime: 0, circuitBreakerState: CircuitBreakerState.CLOSED }; this.circuitBreakerState = CircuitBreakerState.CLOSED; logger.info(`${this.serviceName} initialized`, { config: this.config, serviceName: this.serviceName }); } // Main execution method with resilience patterns async executeWithResilience(operation, context) { const startTime = Date.now(); let lastError; let retryCount = 0; // Check circuit breaker state if (!this.canExecute()) { this.metrics.totalRequests++; this.metrics.errorCount++; return { success: false, error: 'Service circuit breaker is open', metadata: { responseTime: Date.now() - startTime, retryCount: 0, timestamp: new Date() } }; } for (let attempt = 0; attempt <= this.config.retries; attempt++) { try { this.metrics.totalRequests++; retryCount = attempt; // Execute with timeout const result = await ErrorHandler.executeWithTimeout(operation, this.config.timeout, context); // Success - update metrics and circuit breaker const responseTime = Date.now() - startTime; this.updateMetrics(true, responseTime); this.resetCircuitBreaker(); logger.debug(`${this.serviceName} operation succeeded`, { serviceName: this.serviceName, responseTime, retryCount, requestId: context.requestId }); return { success: true, data: result, metadata: { responseTime, retryCount, timestamp: new Date() } }; } catch (error) { lastError = error; const responseTime = Date.now() - startTime; logger.warn(`${this.serviceName} operation failed (attempt ${attempt + 1}/${this.config.retries + 1})`, { serviceName: this.serviceName, error: error.message, responseTime, retryCount: attempt, requestId: context.requestId }); // Don't retry for certain error types if (this.shouldNotRetry(error)) { logger.info(`${this.serviceName} error is not retryable`, { serviceName: this.serviceName, error: error.message, requestId: context.requestId }); break; } // If not the last attempt, wait before retry if (attempt < this.config.retries) { const delay = this.calculateRetryDelay(attempt); await new Promise(resolve => setTimeout(resolve, delay)); } } } // All retries failed const responseTime = Date.now() - startTime; this.updateMetrics(false, responseTime); this.checkCircuitBreaker(); const standardError = ErrorHandler.handleError(lastError, context); return { success: false, error: standardError.message, metadata: { responseTime, retryCount, timestamp: new Date() } }; } // Circuit breaker logic canExecute() { switch (this.circuitBreakerState) { case CircuitBreakerState.CLOSED: return true; case CircuitBreakerState.OPEN: if (this.circuitBreakerOpenTime) { const timeElapsed = Date.now() - this.circuitBreakerOpenTime.getTime(); if (timeElapsed >= this.config.circuitBreakerTimeout) { this.circuitBreakerState = CircuitBreakerState.HALF_OPEN; logger.info(`${this.serviceName} circuit breaker transitioning to HALF_OPEN`, { serviceName: this.serviceName }); return true; } } return false; case CircuitBreakerState.HALF_OPEN: return true; default: return false; } } checkCircuitBreaker() { if (this.circuitBreakerState === CircuitBreakerState.HALF_OPEN) { // If we failed in half-open state, go back to open this.circuitBreakerState = CircuitBreakerState.OPEN; this.circuitBreakerOpenTime = new Date(); logger.warn(`${this.serviceName} circuit breaker back to OPEN after failure in HALF_OPEN`, { serviceName: this.serviceName }); } else if (this.circuitBreakerState === CircuitBreakerState.CLOSED) { // Check if we should open the circuit breaker if (this.metrics.errorCount >= this.config.circuitBreakerThreshold) { this.circuitBreakerState = CircuitBreakerState.OPEN; this.circuitBreakerOpenTime = new Date(); logger.warn(`${this.serviceName} circuit breaker opened due to error threshold`, { serviceName: this.serviceName, errorCount: this.metrics.errorCount, threshold: this.config.circuitBreakerThreshold }); } } this.metrics.circuitBreakerState = this.circuitBreakerState; } resetCircuitBreaker() { if (this.circuitBreakerState !== CircuitBreakerState.CLOSED) { this.circuitBreakerState = CircuitBreakerState.CLOSED; this.circuitBreakerOpenTime = undefined; this.metrics.circuitBreakerState = CircuitBreakerState.CLOSED; logger.info(`${this.serviceName} circuit breaker reset to CLOSED`, { serviceName: this.serviceName }); } } updateMetrics(success, responseTime) { if (success) { this.metrics.successCount++; } else { this.metrics.errorCount++; this.metrics.lastError = new Date(); } // Update rolling average response time this.responseTimes.push(responseTime); if (this.responseTimes.length > 100) { this.responseTimes.shift(); } this.metrics.averageResponseTime = this.responseTimes.reduce((sum, time) => sum + time, 0) / this.responseTimes.length; } calculateRetryDelay(attempt) { return this.config.retryDelay * Math.pow(2, attempt); } shouldNotRetry(error) { // Don't retry for certain HTTP status codes const nonRetryableStatuses = [400, 401, 403, 404, 422]; if (error.response && nonRetryableStatuses.includes(error.response.status)) { return true; } // Don't retry for validation errors if (error.message && error.message.includes('validation')) { return true; } return false; } // Public methods for monitoring and management getMetrics() { return { ...this.metrics }; } async getHealthStatus() { try { // Get service-specific health check const healthStatus = await this.healthCheck(); // Override with circuit breaker state if necessary if (this.circuitBreakerState === CircuitBreakerState.OPEN) { return ServiceHealthStatus.UNHEALTHY; } // Check error rate const errorRate = this.metrics.totalRequests > 0 ? this.metrics.errorCount / this.metrics.totalRequests : 0; if (errorRate > 0.1) { // More than 10% error rate return ServiceHealthStatus.DEGRADED; } // Check response time const timeoutThreshold = this.config.timeout * 0.8; // 80% of timeout if (this.metrics.averageResponseTime > timeoutThreshold) { return ServiceHealthStatus.DEGRADED; } return healthStatus; } catch (error) { logger.error(`Health check failed for ${this.serviceName}`, { serviceName: this.serviceName, error: error.message }); return ServiceHealthStatus.UNHEALTHY; } } resetMetrics() { this.metrics = { totalRequests: 0, successCount: 0, errorCount: 0, averageResponseTime: 0, circuitBreakerState: this.circuitBreakerState }; this.responseTimes = []; logger.info(`${this.serviceName} metrics reset`, { serviceName: this.serviceName }); } getServiceName() { return this.serviceName; } getConfig() { return { ...this.config }; } // Force circuit breaker state (for testing) setCircuitBreakerState(state) { this.circuitBreakerState = state; this.metrics.circuitBreakerState = state; if (state === CircuitBreakerState.OPEN) { this.circuitBreakerOpenTime = new Date(); } else { this.circuitBreakerOpenTime = undefined; } logger.info(`${this.serviceName} circuit breaker state manually set to ${state}`, { serviceName: this.serviceName, state }); } } //# sourceMappingURL=base-service.js.map