Skip to main content
Glama
healthCheck.ts5.78 kB
import { logger } from "../utils/logger.js"; import type { PostgresManagerService } from "./postgresManager.js"; import type { RedisManagerService } from "./redisManager.js"; import type { KeycloakManagerService } from "./keycloakManager.js"; export type HealthStatus = "healthy" | "degraded" | "unhealthy"; export interface ServiceHealth { readonly status: HealthStatus; readonly message?: string; readonly responseTimeMs?: number; } export interface HealthCheckResult { readonly status: HealthStatus; readonly timestamp: string; readonly uptime: number; readonly lastHeartbeat?: string; readonly services: { readonly postgres: ServiceHealth; readonly redis: ServiceHealth; readonly keycloak: ServiceHealth; readonly nginx: ServiceHealth; }; readonly system: { readonly memory: { readonly usedMB: number; readonly totalMB: number; readonly percentUsed: number; }; readonly cpu: { readonly user: number; readonly system: number; }; }; } /** * HealthCheckService provides health status for monitoring */ export class HealthCheckService { private lastHeartbeat: Date | null = null; public constructor( private readonly postgresManager: PostgresManagerService | null, private readonly redisManager: RedisManagerService | null, private readonly keycloakManager: KeycloakManagerService | null, ) { logger.info("HealthCheckService initialized"); } /** * Update last heartbeat timestamp (called by AutoDiscoveryService) */ public recordHeartbeat(): void { this.lastHeartbeat = new Date(); } /** * Get comprehensive health status */ public async getHealth(): Promise<HealthCheckResult> { const services = { postgres: await this.checkPostgres(), redis: await this.checkRedis(), keycloak: await this.checkKeycloak(), nginx: await this.checkNginx(), }; // Determine overall status const serviceStatuses = Object.values(services).map(s => s.status); let overallStatus: HealthStatus = "healthy"; if (serviceStatuses.includes("unhealthy")) { overallStatus = "unhealthy"; } else if (serviceStatuses.includes("degraded")) { overallStatus = "degraded"; } // Get memory usage const memUsage = process.memoryUsage(); const totalMem = memUsage.heapTotal; const usedMem = memUsage.heapUsed; // Get CPU usage const cpuUsage = process.cpuUsage(); return { status: overallStatus, timestamp: new Date().toISOString(), uptime: process.uptime(), lastHeartbeat: this.lastHeartbeat?.toISOString(), services, system: { memory: { usedMB: Math.round(usedMem / 1024 / 1024), totalMB: Math.round(totalMem / 1024 / 1024), percentUsed: Math.round((usedMem / totalMem) * 100), }, cpu: { user: cpuUsage.user, system: cpuUsage.system, }, }, }; } /** * Check PostgreSQL health */ private async checkPostgres(): Promise<ServiceHealth> { if (!this.postgresManager) { return { status: "degraded", message: "PostgreSQL service not configured", }; } const start = Date.now(); try { // Try to get connection stats as a health check await this.postgresManager.getActiveConnections(); return { status: "healthy", responseTimeMs: Date.now() - start, }; } catch (error) { logger.error("PostgreSQL health check failed", { error }); return { status: "unhealthy", message: error instanceof Error ? error.message : String(error), responseTimeMs: Date.now() - start, }; } } /** * Check Redis health */ private async checkRedis(): Promise<ServiceHealth> { if (!this.redisManager) { return { status: "degraded", message: "Redis service not configured", }; } const start = Date.now(); try { // Try to ping Redis await this.redisManager.ping(); return { status: "healthy", responseTimeMs: Date.now() - start, }; } catch (error) { logger.error("Redis health check failed", { error }); return { status: "unhealthy", message: error instanceof Error ? error.message : String(error), responseTimeMs: Date.now() - start, }; } } /** * Check Keycloak health */ private async checkKeycloak(): Promise<ServiceHealth> { if (!this.keycloakManager) { return { status: "degraded", message: "Keycloak service not configured", }; } const start = Date.now(); try { // Try to get realm stats as a health check await this.keycloakManager.getRealmStats(); return { status: "healthy", responseTimeMs: Date.now() - start, }; } catch (error) { logger.error("Keycloak health check failed", { error }); return { status: "unhealthy", message: error instanceof Error ? error.message : String(error), responseTimeMs: Date.now() - start, }; } } /** * Check NGINX health (basic file check) */ private async checkNginx(): Promise<ServiceHealth> { try { // Simple check - just verify NGINX config path exists const { accessSync, constants } = await import("node:fs"); const configPath = process.env.NGINX_CONFIG_PATH ?? "/etc/nginx/nginx.conf"; accessSync(configPath, constants.R_OK); return { status: "healthy", message: "NGINX config accessible", }; } catch (error) { return { status: "degraded", message: "NGINX config not accessible", }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/acampkin95/MCPCentralManager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server