348 lines
7.8 KiB
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(),
|
|
})
|
|
}
|