veza/veza-backend-api/internal/handlers/status_handler.go

348 lines
7.8 KiB
Go

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(),
})
}