package middleware import ( "github.com/gin-gonic/gin" "go.uber.org/zap" "veza-backend-api/internal/tracing" ) const ( // TraceIDHeader est le nom du header HTTP pour propager le trace ID (legacy) TraceIDHeader = "X-Trace-ID" // TraceIDKey est la clé utilisée pour stocker le trace ID dans le contexte Gin TraceIDKey = "trace_id" // SpanIDHeader est le nom du header HTTP pour propager le span ID (legacy) SpanIDHeader = "X-Span-ID" // SpanIDKey est la clé utilisée pour stocker le span ID dans le contexte Gin SpanIDKey = "span_id" ) // Tracing middleware pour générer et propager trace ID (W3C Trace Context compatible) // BE-SVC-018: Amélioré pour supporter le format W3C Trace Context standard // Le trace ID permet de tracer une requête à travers plusieurs services // Si un trace ID est déjà présent dans le header, il est réutilisé (propagation) // Sinon, un nouveau trace ID UUID v4 est généré func Tracing() gin.HandlerFunc { return func(c *gin.Context) { // Extraire le contexte de tracing depuis les headers HTTP traceCtx := tracing.ExtractTraceContext(c.Request) // Stocker dans le contexte Gin pour utilisation dans les handlers et logs c.Set(TraceIDKey, traceCtx.TraceID) c.Set(SpanIDKey, traceCtx.SpanID) // Stocker aussi dans le contexte Go pour propagation ctx := tracing.WithTraceContext(c.Request.Context(), traceCtx) c.Request = c.Request.WithContext(ctx) // Propager via les headers de réponse (format W3C + legacy pour compatibilité) c.Header(tracing.TraceParentHeader, traceCtx.ToW3CTraceParent()) c.Header(TraceIDHeader, traceCtx.TraceID) c.Header(SpanIDHeader, traceCtx.SpanID) c.Next() } } // TracingWithLogger est une version améliorée qui log aussi le contexte de tracing func TracingWithLogger(logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { // Extraire le contexte de tracing depuis les headers HTTP traceCtx := tracing.ExtractTraceContext(c.Request) // Stocker dans le contexte Gin c.Set(TraceIDKey, traceCtx.TraceID) c.Set(SpanIDKey, traceCtx.SpanID) // Stocker dans le contexte Go ctx := tracing.WithTraceContext(c.Request.Context(), traceCtx) c.Request = c.Request.WithContext(ctx) // Propager via les headers de réponse c.Header(tracing.TraceParentHeader, traceCtx.ToW3CTraceParent()) c.Header(TraceIDHeader, traceCtx.TraceID) c.Header(SpanIDHeader, traceCtx.SpanID) // Logger le début de la requête avec le contexte de tracing if logger != nil { logger.Debug("Request started", zap.String("trace_id", traceCtx.TraceID), zap.String("span_id", traceCtx.SpanID), zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), ) } c.Next() } } // GetTraceID retourne le trace ID du contexte, ou une chaîne vide si non défini func GetTraceID(c *gin.Context) string { // Essayer d'abord depuis le contexte Go (nouveau système) if traceCtx := tracing.FromContext(c.Request.Context()); traceCtx != nil { return traceCtx.TraceID } // Fallback sur le contexte Gin (legacy) if traceID, exists := c.Get(TraceIDKey); exists { if id, ok := traceID.(string); ok { return id } } return "" } // GetSpanID retourne le span ID du contexte, ou une chaîne vide si non défini func GetSpanID(c *gin.Context) string { // Essayer d'abord depuis le contexte Go (nouveau système) if traceCtx := tracing.FromContext(c.Request.Context()); traceCtx != nil { return traceCtx.SpanID } // Fallback sur le contexte Gin (legacy) if spanID, exists := c.Get(SpanIDKey); exists { if id, ok := spanID.(string); ok { return id } } return "" } // GetTraceContext retourne le contexte de tracing complet depuis le contexte Gin func GetTraceContext(c *gin.Context) *tracing.TraceContext { // Essayer d'abord depuis le contexte Go if traceCtx := tracing.FromContext(c.Request.Context()); traceCtx != nil { return traceCtx } // Fallback: créer depuis les valeurs du contexte Gin traceID := GetTraceID(c) spanID := GetSpanID(c) if traceID == "" && spanID == "" { return nil } return &tracing.TraceContext{ TraceID: traceID, SpanID: spanID, Sampled: true, } } // NewChildSpan crée un nouveau span enfant depuis le contexte Gin func NewChildSpan(c *gin.Context) *tracing.TraceContext { parentCtx := GetTraceContext(c) if parentCtx == nil { return tracing.NewTraceContext() } return parentCtx.NewChildSpan() }