Files
MyClub/internal/controllers/health_controller.go
T
Tomáš Dvořák 12cba639b9 upload
2025-10-16 13:32:05 +02:00

266 lines
6.4 KiB
Go

package controllers
import (
"context"
"net/http"
"runtime"
"time"
"fotbal-club/internal/services"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// HealthController handles health check endpoints
type HealthController struct {
DB *gorm.DB
}
// HealthResponse represents health check response
type HealthResponse struct {
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
Version string `json:"version,omitempty"`
Checks map[string]CheckResult `json:"checks"`
System SystemInfo `json:"system,omitempty"`
}
// CheckResult represents individual health check result
type CheckResult struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
Latency time.Duration `json:"latency_ms,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
// SystemInfo represents system resource information
type SystemInfo struct {
Goroutines int `json:"goroutines"`
MemoryAlloc uint64 `json:"memory_alloc_mb"`
MemoryTotal uint64 `json:"memory_total_mb"`
MemorySys uint64 `json:"memory_sys_mb"`
NumCPU int `json:"num_cpu"`
GoVersion string `json:"go_version"`
}
// Liveness returns a simple liveness probe
func (hc *HealthController) Liveness(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "alive",
"timestamp": time.Now(),
})
}
// Readiness returns detailed readiness probe
func (hc *HealthController) Readiness(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
defer cancel()
checks := make(map[string]CheckResult)
overallStatus := "healthy"
// Check database
dbCheck := hc.checkDatabase(ctx)
checks["database"] = dbCheck
if dbCheck.Status != "healthy" {
overallStatus = "unhealthy"
}
// Check cache
cacheCheck := hc.checkCache(ctx)
checks["cache"] = cacheCheck
if cacheCheck.Status != "degraded" && cacheCheck.Status != "healthy" {
overallStatus = "unhealthy"
}
// Check disk space
diskCheck := hc.checkDiskSpace()
checks["disk"] = diskCheck
if diskCheck.Status != "healthy" {
overallStatus = "degraded"
}
response := HealthResponse{
Status: overallStatus,
Timestamp: time.Now(),
Checks: checks,
}
statusCode := http.StatusOK
if overallStatus == "unhealthy" {
statusCode = http.StatusServiceUnavailable
}
c.JSON(statusCode, response)
}
// Health returns comprehensive health information
func (hc *HealthController) Health(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
defer cancel()
checks := make(map[string]CheckResult)
overallStatus := "healthy"
// All checks
dbCheck := hc.checkDatabase(ctx)
checks["database"] = dbCheck
if dbCheck.Status != "healthy" {
overallStatus = "unhealthy"
}
cacheCheck := hc.checkCache(ctx)
checks["cache"] = cacheCheck
diskCheck := hc.checkDiskSpace()
checks["disk"] = diskCheck
// System info
sysInfo := hc.getSystemInfo()
response := HealthResponse{
Status: overallStatus,
Timestamp: time.Now(),
Version: "1.0.0", // Use actual version
Checks: checks,
System: sysInfo,
}
statusCode := http.StatusOK
if overallStatus == "unhealthy" {
statusCode = http.StatusServiceUnavailable
}
c.JSON(statusCode, response)
}
// checkDatabase verifies database connectivity
func (hc *HealthController) checkDatabase(ctx context.Context) CheckResult {
start := time.Now()
sqlDB, err := hc.DB.DB()
if err != nil {
return CheckResult{
Status: "unhealthy",
Message: "Cannot get database connection: " + err.Error(),
Timestamp: time.Now(),
}
}
err = sqlDB.PingContext(ctx)
latency := time.Since(start)
if err != nil {
return CheckResult{
Status: "unhealthy",
Message: "Database ping failed: " + err.Error(),
Latency: latency,
Timestamp: time.Now(),
}
}
// Check connection pool stats
stats := sqlDB.Stats()
if stats.OpenConnections >= stats.MaxOpenConnections {
return CheckResult{
Status: "degraded",
Message: "Connection pool at maximum capacity",
Latency: latency,
Timestamp: time.Now(),
}
}
return CheckResult{
Status: "healthy",
Message: "Database connection successful",
Latency: latency,
Timestamp: time.Now(),
}
}
// checkCache verifies cache service
func (hc *HealthController) checkCache(ctx context.Context) CheckResult {
start := time.Now()
cache := services.GetCacheService()
// Test cache write/read
testKey := "health:check"
testValue := "ok"
err := cache.Set(testKey, testValue, 1*time.Minute)
if err != nil {
return CheckResult{
Status: "unhealthy",
Message: "Cache write failed: " + err.Error(),
Latency: time.Since(start),
Timestamp: time.Now(),
}
}
var result string
err = cache.Get(testKey, &result)
if err != nil {
return CheckResult{
Status: "unhealthy",
Message: "Cache read failed: " + err.Error(),
Latency: time.Since(start),
Timestamp: time.Now(),
}
}
cache.Delete(testKey)
return CheckResult{
Status: "healthy",
Message: "Cache operational",
Latency: time.Since(start),
Timestamp: time.Now(),
}
}
// checkDiskSpace checks available disk space
func (hc *HealthController) checkDiskSpace() CheckResult {
// This is a simplified check
// In production, implement proper disk space monitoring
return CheckResult{
Status: "healthy",
Message: "Disk space sufficient",
Timestamp: time.Now(),
}
}
// getSystemInfo collects system resource information
func (hc *HealthController) getSystemInfo() SystemInfo {
var m runtime.MemStats
runtime.ReadMemStats(&m)
return SystemInfo{
Goroutines: runtime.NumGoroutine(),
MemoryAlloc: m.Alloc / 1024 / 1024, // MB
MemoryTotal: m.TotalAlloc / 1024 / 1024, // MB
MemorySys: m.Sys / 1024 / 1024, // MB
NumCPU: runtime.NumCPU(),
GoVersion: runtime.Version(),
}
}
// Metrics returns Prometheus-compatible metrics
func (hc *HealthController) Metrics(c *gin.Context) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// Simple text-based metrics
metrics := ""
metrics += "# HELP go_goroutines Number of goroutines\n"
metrics += "# TYPE go_goroutines gauge\n"
metrics += "go_goroutines " + string(rune(runtime.NumGoroutine())) + "\n\n"
metrics += "# HELP go_memory_alloc_bytes Allocated memory in bytes\n"
metrics += "# TYPE go_memory_alloc_bytes gauge\n"
metrics += "go_memory_alloc_bytes " + string(rune(m.Alloc)) + "\n\n"
c.String(http.StatusOK, metrics)
}