197 lines
5.2 KiB
Go
197 lines
5.2 KiB
Go
package tracing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const (
|
|
// W3C Trace Context headers
|
|
TraceParentHeader = "traceparent"
|
|
TraceStateHeader = "tracestate"
|
|
|
|
// Legacy headers (for backward compatibility)
|
|
LegacyTraceIDHeader = "X-Trace-ID"
|
|
LegacySpanIDHeader = "X-Span-ID"
|
|
)
|
|
|
|
// TraceContext représente un contexte de tracing distribué (BE-SVC-018)
|
|
type TraceContext struct {
|
|
TraceID string
|
|
SpanID string
|
|
ParentSpan string // ID du span parent (pour hiérarchie)
|
|
Sampled bool // Indique si le trace doit être échantillonné
|
|
}
|
|
|
|
// NewTraceContext crée un nouveau contexte de tracing
|
|
func NewTraceContext() *TraceContext {
|
|
return &TraceContext{
|
|
TraceID: uuid.New().String(),
|
|
SpanID: uuid.New().String(),
|
|
Sampled: true, // Par défaut, on échantillonne tous les traces
|
|
}
|
|
}
|
|
|
|
// NewChildSpan crée un nouveau span enfant à partir d'un contexte parent
|
|
func (tc *TraceContext) NewChildSpan() *TraceContext {
|
|
return &TraceContext{
|
|
TraceID: tc.TraceID, // Même trace ID
|
|
SpanID: uuid.New().String(),
|
|
ParentSpan: tc.SpanID, // Le span actuel devient le parent
|
|
Sampled: tc.Sampled,
|
|
}
|
|
}
|
|
|
|
// ToW3CTraceParent convertit le contexte en format W3C Trace Context (traceparent)
|
|
// Format: version-trace_id-parent_id-trace_flags
|
|
// Exemple: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
|
|
func (tc *TraceContext) ToW3CTraceParent() string {
|
|
version := "00" // Version 00 (actuelle)
|
|
traceID := formatTraceID(tc.TraceID)
|
|
parentID := formatSpanID(tc.ParentSpan)
|
|
if parentID == "" {
|
|
parentID = "0000000000000000" // Pas de parent
|
|
}
|
|
flags := "01" // Sampled flag
|
|
if !tc.Sampled {
|
|
flags = "00"
|
|
}
|
|
return fmt.Sprintf("%s-%s-%s-%s", version, traceID, parentID, flags)
|
|
}
|
|
|
|
// FromW3CTraceParent parse un header traceparent W3C
|
|
func FromW3CTraceParent(traceParent string) (*TraceContext, error) {
|
|
parts := strings.Split(traceParent, "-")
|
|
if len(parts) != 4 {
|
|
return nil, fmt.Errorf("invalid traceparent format")
|
|
}
|
|
|
|
version := parts[0]
|
|
if version != "00" {
|
|
return nil, fmt.Errorf("unsupported traceparent version: %s", version)
|
|
}
|
|
|
|
traceID := parts[1]
|
|
parentID := parts[2]
|
|
flags := parts[3]
|
|
|
|
// Parser les flags (dernier caractère = sampled flag)
|
|
sampled := flags[len(flags)-1] == '1'
|
|
|
|
tc := &TraceContext{
|
|
TraceID: traceID,
|
|
SpanID: uuid.New().String(), // Nouveau span ID pour ce service
|
|
ParentSpan: parentID,
|
|
Sampled: sampled,
|
|
}
|
|
|
|
return tc, nil
|
|
}
|
|
|
|
// formatTraceID formate un trace ID pour W3C (32 caractères hex)
|
|
func formatTraceID(id string) string {
|
|
// Supprimer les tirets si c'est un UUID
|
|
id = strings.ReplaceAll(id, "-", "")
|
|
// S'assurer que c'est exactement 32 caractères hex
|
|
if len(id) > 32 {
|
|
id = id[:32]
|
|
} else if len(id) < 32 {
|
|
// Padding avec des zéros
|
|
id = id + strings.Repeat("0", 32-len(id))
|
|
}
|
|
return id
|
|
}
|
|
|
|
// formatSpanID formate un span ID pour W3C (16 caractères hex)
|
|
func formatSpanID(id string) string {
|
|
if id == "" {
|
|
return ""
|
|
}
|
|
// Supprimer les tirets si c'est un UUID
|
|
id = strings.ReplaceAll(id, "-", "")
|
|
// S'assurer que c'est exactement 16 caractères hex
|
|
if len(id) > 16 {
|
|
id = id[:16]
|
|
} else if len(id) < 16 {
|
|
// Padding avec des zéros
|
|
id = id + strings.Repeat("0", 16-len(id))
|
|
}
|
|
return id
|
|
}
|
|
|
|
// InjectTraceContext injecte le contexte de tracing dans les headers HTTP
|
|
func InjectTraceContext(req *http.Request, tc *TraceContext) {
|
|
if tc == nil {
|
|
return
|
|
}
|
|
|
|
// Injecter le format W3C Trace Context (standard)
|
|
req.Header.Set(TraceParentHeader, tc.ToW3CTraceParent())
|
|
|
|
// Injecter aussi les headers legacy pour compatibilité
|
|
req.Header.Set(LegacyTraceIDHeader, tc.TraceID)
|
|
req.Header.Set(LegacySpanIDHeader, tc.SpanID)
|
|
}
|
|
|
|
// ExtractTraceContext extrait le contexte de tracing depuis les headers HTTP
|
|
func ExtractTraceContext(req *http.Request) *TraceContext {
|
|
// Essayer d'abord le format W3C
|
|
if traceParent := req.Header.Get(TraceParentHeader); traceParent != "" {
|
|
if tc, err := FromW3CTraceParent(traceParent); err == nil {
|
|
return tc
|
|
}
|
|
}
|
|
|
|
// Fallback sur les headers legacy
|
|
traceID := req.Header.Get(LegacyTraceIDHeader)
|
|
spanID := req.Header.Get(LegacySpanIDHeader)
|
|
|
|
if traceID == "" && spanID == "" {
|
|
// Aucun contexte, créer un nouveau
|
|
return NewTraceContext()
|
|
}
|
|
|
|
// Créer un contexte à partir des headers legacy
|
|
if traceID == "" {
|
|
traceID = uuid.New().String()
|
|
}
|
|
if spanID == "" {
|
|
spanID = uuid.New().String()
|
|
}
|
|
|
|
return &TraceContext{
|
|
TraceID: traceID,
|
|
SpanID: spanID,
|
|
Sampled: true,
|
|
}
|
|
}
|
|
|
|
// ContextKey est le type pour la clé de contexte
|
|
type contextKey string
|
|
|
|
const traceContextKey contextKey = "trace_context"
|
|
|
|
// WithTraceContext ajoute un contexte de tracing au contexte Go
|
|
func WithTraceContext(ctx context.Context, tc *TraceContext) context.Context {
|
|
return context.WithValue(ctx, traceContextKey, tc)
|
|
}
|
|
|
|
// FromContext extrait le contexte de tracing depuis le contexte Go
|
|
func FromContext(ctx context.Context) *TraceContext {
|
|
if tc, ok := ctx.Value(traceContextKey).(*TraceContext); ok {
|
|
return tc
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetOrCreateTraceContext obtient le contexte de tracing depuis le contexte Go, ou en crée un nouveau
|
|
func GetOrCreateTraceContext(ctx context.Context) *TraceContext {
|
|
if tc := FromContext(ctx); tc != nil {
|
|
return tc
|
|
}
|
|
return NewTraceContext()
|
|
}
|