package handlers import ( "context" "net/http" "runtime" "time" "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" "veza-backend-api/internal/database" "veza-backend-api/internal/monitoring" ) var ( // startTime tracks when the server started startTime = time.Now() ) // StatusResponse représente la réponse complète du status endpoint type StatusResponse struct { Status string `json:"status"` UptimeSec int64 `json:"uptime_seconds"` Services map[string]ServiceInfo `json:"services"` Version string `json:"version"` GitCommit string `json:"git_commit"` BuildTime string `json:"build_time"` Environment string `json:"environment,omitempty"` } // ServiceInfo représente l'état d'un service type ServiceInfo struct { Status string `json:"status"` Latency float64 `json:"latency_ms,omitempty"` Message string `json:"message,omitempty"` } // StatusHandler gère les endpoints de status type StatusHandler struct { db *gorm.DB logger *zap.Logger redis *redis.Client chatServerURL string streamServerURL string version string gitCommit string buildTime string environment string } // NewStatusHandler crée un nouveau handler de status func NewStatusHandler( db *gorm.DB, logger *zap.Logger, redisClient interface{}, chatServerURL string, streamServerURL string, version string, gitCommit string, buildTime string, environment string, ) *StatusHandler { h := &StatusHandler{ db: db, logger: logger, chatServerURL: chatServerURL, streamServerURL: streamServerURL, version: version, gitCommit: gitCommit, buildTime: buildTime, environment: environment, } // Type assertion for Redis if r, ok := redisClient.(*redis.Client); ok { h.redis = r } return h } // GetStatus retourne le status complet de l'application func (h *StatusHandler) GetStatus(c *gin.Context) { response := StatusResponse{ Status: "ok", UptimeSec: int64(time.Since(startTime).Seconds()), Services: make(map[string]ServiceInfo), Version: h.version, GitCommit: h.gitCommit, BuildTime: h.buildTime, } if h.environment != "" { response.Environment = h.environment } // Check database dbInfo := h.checkDatabase() response.Services["database"] = dbInfo // Check Redis redisInfo := h.checkRedis() response.Services["redis"] = redisInfo // Check chat server (if configured) if h.chatServerURL != "" { chatInfo := h.checkChatServer(c.Request.Context()) response.Services["chat_server"] = chatInfo } // Check stream server (if configured) if h.streamServerURL != "" { streamInfo := h.checkStreamServer(c.Request.Context()) response.Services["stream_server"] = streamInfo } // Déterminer le statut global globalStatus := "ok" for _, service := range response.Services { if service.Status == "error" { globalStatus = "degraded" break } if service.Status == "slow" { if globalStatus != "degraded" { globalStatus = "degraded" } } } response.Status = globalStatus statusCode := http.StatusOK if globalStatus == "degraded" { statusCode = http.StatusServiceUnavailable } RespondSuccess(c, statusCode, response) } // checkDatabase vérifie la connexion à la base de données func (h *StatusHandler) checkDatabase() ServiceInfo { start := time.Now() err := database.IsConnectionHealthy(h.db, 5*time.Second) duration := time.Since(start) latencyMs := float64(duration.Nanoseconds()) / 1e6 if err != nil { monitoring.RecordHealthCheck("database", latencyMs, "error") return ServiceInfo{ Status: "error", Message: err.Error(), Latency: latencyMs, } } status := "ok" if latencyMs > 100 { status = "slow" } monitoring.RecordHealthCheck("database", latencyMs, status) return ServiceInfo{ Status: status, Latency: latencyMs, } } // checkRedis vérifie la connexion à Redis func (h *StatusHandler) checkRedis() ServiceInfo { start := time.Now() if h.redis == nil { monitoring.RecordHealthCheck("redis", 0, "error") return ServiceInfo{ Status: "error", Message: "Redis connection not configured", } } ctx, cancel := context.WithTimeout(context.Background(), 400*time.Millisecond) defer cancel() _, err := h.redis.Ping(ctx).Result() duration := time.Since(start) latencyMs := float64(duration.Nanoseconds()) / 1e6 if err != nil { monitoring.RecordHealthCheck("redis", latencyMs, "error") return ServiceInfo{ Status: "error", Message: err.Error(), Latency: latencyMs, } } status := "ok" if latencyMs > 50 { status = "slow" } monitoring.RecordHealthCheck("redis", latencyMs, status) return ServiceInfo{ Status: status, Latency: latencyMs, } } // checkChatServer vérifie la disponibilité du chat server func (h *StatusHandler) checkChatServer(ctx context.Context) ServiceInfo { start := time.Now() client := &http.Client{ Timeout: 400 * time.Millisecond, } url := h.chatServerURL if url[len(url)-1] != '/' { url += "/" } url += "health" req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return ServiceInfo{ Status: "error", Message: err.Error(), Latency: 0, } } resp, err := client.Do(req) duration := time.Since(start) latencyMs := float64(duration.Nanoseconds()) / 1e6 if err != nil { monitoring.RecordHealthCheck("chat_server", latencyMs, "error") return ServiceInfo{ Status: "error", Message: err.Error(), Latency: latencyMs, } } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { monitoring.RecordHealthCheck("chat_server", latencyMs, "error") return ServiceInfo{ Status: "error", Message: "chat server returned non-200 status", Latency: latencyMs, } } status := "ok" if latencyMs > 100 { status = "slow" } monitoring.RecordHealthCheck("chat_server", latencyMs, status) return ServiceInfo{ Status: status, Latency: latencyMs, } } // checkStreamServer vérifie la disponibilité du stream server func (h *StatusHandler) checkStreamServer(ctx context.Context) ServiceInfo { start := time.Now() client := &http.Client{ Timeout: 400 * time.Millisecond, } url := h.streamServerURL if url[len(url)-1] != '/' { url += "/" } url += "health" req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return ServiceInfo{ Status: "error", Message: err.Error(), Latency: 0, } } resp, err := client.Do(req) duration := time.Since(start) latencyMs := float64(duration.Nanoseconds()) / 1e6 if err != nil { monitoring.RecordHealthCheck("stream_server", latencyMs, "error") return ServiceInfo{ Status: "error", Message: err.Error(), Latency: latencyMs, } } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { monitoring.RecordHealthCheck("stream_server", latencyMs, "error") return ServiceInfo{ Status: "error", Message: "stream server returned non-200 status", Latency: latencyMs, } } status := "ok" if latencyMs > 100 { status = "slow" } monitoring.RecordHealthCheck("stream_server", latencyMs, status) return ServiceInfo{ Status: status, Latency: latencyMs, } } // GetSystemInfo retourne des informations système (pour debug) func (h *StatusHandler) GetSystemInfo(c *gin.Context) { var m runtime.MemStats runtime.ReadMemStats(&m) // Utiliser la fonction bToMb définie dans system_metrics.go bToMb := func(b uint64) uint64 { return b / 1024 / 1024 } RespondSuccess(c, http.StatusOK, gin.H{ "uptime_seconds": int64(time.Since(startTime).Seconds()), "memory": gin.H{ "alloc_mb": bToMb(m.Alloc), "total_alloc_mb": bToMb(m.TotalAlloc), "sys_mb": bToMb(m.Sys), "num_gc": m.NumGC, }, "goroutines": runtime.NumGoroutine(), }) }