114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
package middleware
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// RequestLogger middleware pour logger les requêtes HTTP avec contexte structuré
|
|
func RequestLogger(logger *zap.Logger) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Début de la requête
|
|
start := time.Now()
|
|
path := c.Request.URL.Path
|
|
query := c.Request.URL.RawQuery
|
|
|
|
// Traiter la requête
|
|
c.Next()
|
|
|
|
// Calculer la durée
|
|
latency := time.Since(start)
|
|
|
|
// Récupérer le request ID si présent
|
|
requestID, exists := c.Get("request_id")
|
|
if !exists {
|
|
requestID = ""
|
|
}
|
|
|
|
// Récupérer l'user ID si présent (après authentification)
|
|
userID, exists := c.Get("user_id")
|
|
if !exists {
|
|
userID = nil
|
|
}
|
|
|
|
// Préparer les champs structurés
|
|
fields := []zap.Field{
|
|
zap.Int("status", c.Writer.Status()),
|
|
zap.String("method", c.Request.Method),
|
|
zap.String("path", path),
|
|
zap.String("query", query),
|
|
zap.String("ip", c.ClientIP()),
|
|
zap.String("user_agent", c.Request.UserAgent()),
|
|
zap.Duration("latency", latency),
|
|
zap.Int("body_size", c.Writer.Size()),
|
|
}
|
|
|
|
// Ajouter request ID si présent
|
|
if requestID != "" {
|
|
fields = append(fields, zap.String("request_id", requestID.(string)))
|
|
}
|
|
|
|
// Ajouter user ID si présent
|
|
if userID != nil {
|
|
fields = append(fields, zap.Any("user_id", userID))
|
|
}
|
|
|
|
// Ajouter le trace_id au logger si disponible (T0025)
|
|
if traceID := GetTraceID(c); traceID != "" {
|
|
fields = append(fields, zap.String("trace_id", traceID))
|
|
}
|
|
|
|
// Ajouter le span_id au logger si disponible (T0025)
|
|
if spanID := GetSpanID(c); spanID != "" {
|
|
fields = append(fields, zap.String("span_id", spanID))
|
|
}
|
|
|
|
// Ajouter les erreurs s'il y en a
|
|
if len(c.Errors) > 0 {
|
|
fields = append(fields, zap.Strings("errors", c.Errors.Errors()))
|
|
}
|
|
|
|
// FIX #9: Détecter les requêtes lentes avec seuil configurable
|
|
// Seuil par défaut: 1 seconde (configurable via SLOW_REQUEST_THRESHOLD_MS)
|
|
slowThresholdMs := getEnvInt("SLOW_REQUEST_THRESHOLD_MS", 1000) // 1000ms = 1s par défaut
|
|
slowThreshold := time.Duration(slowThresholdMs) * time.Millisecond
|
|
isSlowRequest := latency > slowThreshold
|
|
|
|
// Logger selon le status code et la latence
|
|
if c.Writer.Status() >= 500 {
|
|
// Erreurs serveur
|
|
logger.Error("Request completed", fields...)
|
|
} else if c.Writer.Status() >= 400 {
|
|
// Erreurs client
|
|
logger.Warn("Request completed with error", fields...)
|
|
} else if isSlowRequest {
|
|
// FIX #9: Requêtes lentes (succès mais > seuil)
|
|
logger.Warn("Slow request detected",
|
|
append(fields,
|
|
zap.Duration("slow_threshold", slowThreshold),
|
|
zap.Bool("is_slow", true),
|
|
)...,
|
|
)
|
|
} else {
|
|
// Succès normal
|
|
logger.Info("Request completed", fields...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// getEnvInt récupère une variable d'environnement entière avec une valeur par défaut
|
|
// FIX #9: Helper pour lire SLOW_REQUEST_THRESHOLD_MS
|
|
func getEnvInt(key string, defaultValue int) int {
|
|
value := os.Getenv(key)
|
|
if value == "" {
|
|
return defaultValue
|
|
}
|
|
if intValue, err := strconv.Atoi(value); err == nil {
|
|
return intValue
|
|
}
|
|
return defaultValue
|
|
}
|