mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
266 lines
6.4 KiB
Go
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)
|
|
}
|