import { AwsService } from '../services/aws-service'; import { ClientService } from '../services/client-service'; import { ComponentHealth, HealthStatus, SystemHealth } from '../types/client'; import { logger } from './logger'; /** * Health check manager for AWS Logs MCP client */ export class HealthCheckManager { private startTime: number; private lastCheckResults: Map; private clientService?: ClientService; private awsService: AwsService; private version: string; constructor(version = '0.1.0') { this.startTime = Date.now(); this.lastCheckResults = new Map(); this.awsService = new AwsService(); this.version = version; } /** * Register the client service for health checks * @param clientService The client service instance */ registerClientService(clientService: ClientService): void { this.clientService = clientService; } /** * Get system uptime in seconds * @returns Uptime in seconds */ private getUptime(): number { return Math.floor((Date.now() - this.startTime) / 1000); } /** * Check MCP server connection health * @returns Health status of MCP server connection */ private async checkMcpServerHealth(): Promise { const componentName = 'mcpServer'; const now = new Date().toISOString(); try { if (!this.clientService) { return { name: componentName, status: HealthStatus.UNKNOWN, message: 'Client service not registered', lastChecked: now }; } // Check if client is connected const isConnected = this.clientService.isConnected(); if (!isConnected) { try { // Try to reconnect await this.clientService.connect(); return { name: componentName, status: HealthStatus.DEGRADED, message: 'Connection restored after disconnect', lastChecked: now }; } catch (error) { return { name: componentName, status: HealthStatus.FAILING, message: 'Cannot connect to MCP server', lastChecked: now, details: { error: error instanceof Error ? error.message : String(error) } }; } } // Check tool availability try { const toolsResponse = await this.clientService.listTools(); const toolCount = toolsResponse?.tools?.length || 0; return { name: componentName, status: HealthStatus.HEALTHY, message: `Connected to MCP server with ${toolCount} tools available`, lastChecked: now, details: { toolCount } }; } catch (error) { return { name: componentName, status: HealthStatus.DEGRADED, message: 'Connected to MCP server but tools unavailable', lastChecked: now, details: { error: error instanceof Error ? error.message : String(error) } }; } } catch (error) { logger.error('Error in MCP server health check', { component: 'healthCheck', error: error instanceof Error ? { name: error.name, message: error.message } : error }); return { name: componentName, status: HealthStatus.FAILING, message: 'Error checking MCP server health', lastChecked: now, details: { error: error instanceof Error ? error.message : String(error) } }; } } /** * Check AWS connection health * @returns Health status of AWS connection */ private async checkAwsHealth(): Promise { const componentName = 'awsConnection'; const now = new Date().toISOString(); try { // Test direct AWS connection const result = await this.awsService.testConnection(); if (result.success) { return { name: componentName, status: HealthStatus.HEALTHY, message: result.message, lastChecked: now, details: { logGroupName: result.logGroupName } }; } else { return { name: componentName, status: HealthStatus.FAILING, message: result.message, lastChecked: now, details: { error: result.error instanceof Error ? result.error.message : String(result.error) } }; } } catch (error) { logger.error('Error in AWS health check', { component: 'healthCheck', error: error instanceof Error ? { name: error.name, message: error.message } : error }); return { name: componentName, status: HealthStatus.FAILING, message: 'Error checking AWS connection health', lastChecked: now, details: { error: error instanceof Error ? error.message : String(error) } }; } } /** * Determine overall system health based on component health * @param components List of component health statuses * @returns Overall system health status */ private determineOverallHealth(components: ComponentHealth[]): HealthStatus { // If any component is failing, the overall status is failing if (components.some(c => c.status === HealthStatus.FAILING)) { return HealthStatus.FAILING; } // If any component is degraded, the overall status is degraded if (components.some(c => c.status === HealthStatus.DEGRADED)) { return HealthStatus.DEGRADED; } // If any component is unknown and none are failing/degraded, the status is degraded if (components.some(c => c.status === HealthStatus.UNKNOWN)) { return HealthStatus.DEGRADED; } // If all components are healthy, the overall status is healthy return HealthStatus.HEALTHY; } /** * Run all health checks * @returns Complete system health status */ async checkHealth(): Promise { logger.info('Running health checks...', { component: 'healthCheck' }); // Run all health checks in parallel const [mcpHealth, awsHealth] = await Promise.all([ this.checkMcpServerHealth(), this.checkAwsHealth() ]); // Store results for future reference this.lastCheckResults.set(mcpHealth.name, mcpHealth); this.lastCheckResults.set(awsHealth.name, awsHealth); // Collect all component results const components = [mcpHealth, awsHealth]; // Determine overall health status const overallStatus = this.determineOverallHealth(components); // Build complete system health report const systemHealth: SystemHealth = { status: overallStatus, timestamp: new Date().toISOString(), components, version: this.version, uptime: this.getUptime() }; logger.info('Health check completed', { component: 'healthCheck', status: overallStatus, componentCount: components.length }); return systemHealth; } /** * Get cached health status without running checks * @returns Cached system health or runs a new check if cache is empty */ async getCachedHealth(): Promise { // If we have no cached results, run a fresh check if (this.lastCheckResults.size === 0) { return this.checkHealth(); } // Use cached component results const components = Array.from(this.lastCheckResults.values()); // Determine overall health from cached components const overallStatus = this.determineOverallHealth(components); return { status: overallStatus, timestamp: new Date().toISOString(), components, version: this.version, uptime: this.getUptime() }; } } // Export a singleton instance export const healthCheckManager = new HealthCheckManager();