import { Request, Response } from 'express'; import { cloudWatchLogsClient, cloudTrailClient } from '../config/aws-config.js'; import { DescribeLogGroupsCommand } from '@aws-sdk/client-cloudwatch-logs'; import { getLogger } from '../utils/logging.js'; const logger = getLogger(); /** * Service health status */ export enum HealthStatus { OK = 'ok', DEGRADED = 'degraded', UNHEALTHY = 'unhealthy' } /** * Health check component */ interface HealthCheckComponent { name: string; status: HealthStatus; message?: string; responseTime?: number; lastChecked?: string; } /** * Health check response */ interface HealthCheckResponse { status: HealthStatus; version: string; uptime: number; components: Record; timestamp: string; } // Cache health check results let lastHealthCheck: HealthCheckResponse | null = null; let lastCheckTime = 0; const CACHE_TTL = 30 * 1000; // 30 seconds /** * Check AWS CloudWatch Logs service health */ async function checkCloudWatchLogsHealth(): Promise { const component: HealthCheckComponent = { name: 'CloudWatchLogs', status: HealthStatus.UNHEALTHY, lastChecked: new Date().toISOString() }; const startTime = Date.now(); try { // Simple command to test connectivity const command = new DescribeLogGroupsCommand({ limit: 1 }); await cloudWatchLogsClient.send(command); component.status = HealthStatus.OK; component.message = 'CloudWatch Logs service is reachable'; } catch (error) { component.status = HealthStatus.UNHEALTHY; component.message = `Failed to connect to CloudWatch Logs: ${error instanceof Error ? error.message : String(error)}`; logger.warn('CloudWatch Logs health check failed', { error }); } component.responseTime = Date.now() - startTime; return component; } /** * Check AWS CloudTrail service health */ async function checkCloudTrailHealth(): Promise { const component: HealthCheckComponent = { name: 'CloudTrail', status: HealthStatus.OK, message: 'CloudTrail client initialized', lastChecked: new Date().toISOString() }; // For CloudTrail, we just check if the client is initialized // We don't make an actual API call to avoid unnecessary costs if (!cloudTrailClient) { component.status = HealthStatus.UNHEALTHY; component.message = 'CloudTrail client is not initialized'; } return component; } /** * Check application server health */ function checkServerHealth(): HealthCheckComponent { return { name: 'Server', status: HealthStatus.OK, message: 'Server is running', lastChecked: new Date().toISOString() }; } /** * Perform a health check */ async function performHealthCheck(): Promise { // Check components in parallel const [cloudWatchLogs, cloudTrail, server] = await Promise.all([ checkCloudWatchLogsHealth(), checkCloudTrailHealth(), checkServerHealth() ]); // Combine component results const components = { cloudWatchLogs, cloudTrail, server }; // Determine overall status let overallStatus = HealthStatus.OK; if (Object.values(components).some(c => c.status === HealthStatus.UNHEALTHY)) { overallStatus = HealthStatus.UNHEALTHY; } else if (Object.values(components).some(c => c.status === HealthStatus.DEGRADED)) { overallStatus = HealthStatus.DEGRADED; } return { status: overallStatus, version: process.env.npm_package_version || '0.1.0', uptime: process.uptime(), components, timestamp: new Date().toISOString() }; } /** * Health check endpoint handler */ export async function healthCheckHandler(req: Request, res: Response): Promise { try { // Check if we should use cached result const now = Date.now(); const useCache = !req.query.force && lastHealthCheck && (now - lastCheckTime < CACHE_TTL); let healthCheck: HealthCheckResponse; if (useCache) { healthCheck = lastHealthCheck as HealthCheckResponse; } else { healthCheck = await performHealthCheck(); lastHealthCheck = healthCheck; lastCheckTime = now; } // Set status code based on health status let statusCode = 200; if (healthCheck.status === HealthStatus.DEGRADED) { statusCode = 200; // Still OK, but degraded } else if (healthCheck.status === HealthStatus.UNHEALTHY) { statusCode = 503; // Service Unavailable } res.status(statusCode).json(healthCheck); } catch (error) { logger.error('Error in health check endpoint', { error }); res.status(500).json({ status: HealthStatus.UNHEALTHY, message: 'Health check failed', error: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString() }); } }