- 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%)
137 lines
4.3 KiB
Go
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()
|
|
}
|