veza/veza-backend-api/internal/middleware/tracing.go
senke 0ac3b82962 [BE-SVC-018] be-svc: Implement request tracing
- Created TraceContext struct for distributed tracing
- Implemented W3C Trace Context format support (traceparent header)
- Added backward compatibility with legacy X-Trace-ID and X-Span-ID headers
- Created HTTPClientWithTracing for automatic trace propagation in outgoing requests
- Enhanced Tracing middleware to use new trace context system
- Added context propagation helpers (WithTraceContext, FromContext)
- Added child span creation for nested operations
- Comprehensive unit tests for trace context and HTTP client

Phase: PHASE-6
Priority: P2
Progress: 114/267 (42.70%)
2025-12-24 17:05:32 +01:00

137 lines
4.3 KiB
Go

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