/

Correlation IDs

Correlation IDs enable you to track a single request as it flows through multiple services and systems. This is essential for debugging distributed systems and understanding the full lifecycle of a request.

Correlation ID Basics

A correlation ID is a unique identifier attached to a request that persists across all related log entries:

Go
import "github.com/bytehide/bytehide-logs-go"

// Set correlation ID for a request
correlationId := "req_" + generateUUID()
logs.WithCorrelationID(correlationId).
    WithContext("endpoint", "/api/orders").
    Info("Processing request")

// Same ID appears in all related logs
logs.WithCorrelationID(correlationId).
    WithContext("step", "validation").
    Debug("Validating request")

logs.WithCorrelationID(correlationId).
    WithContext("step", "processing").
    Debug("Processing request")

HTTP Middleware Pattern

The most common pattern is to extract or generate a correlation ID in middleware and apply it to all requests:

Basic Middleware

Go
import (
    "net/http"
    "github.com/bytehide/bytehide-logs-go"
)

// Generate or extract correlation ID
func correlationIdMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        correlationId := extractOrGenerateCorrelationID(r)
        
        logs.WithCorrelationID(correlationId).
            WithContext("method", r.Method).
            WithContext("path", r.RequestURI).
            WithContext("remoteAddr", r.RemoteAddr).
            Debug("Incoming request")
        
        // Store in context for downstream handlers
        ctx := r.Context()
        ctx = context.WithValue(ctx, "correlationId", correlationId)
        r = r.WithContext(ctx)
        
        next.ServeHTTP(w, r)
        
        logs.WithCorrelationID(correlationId).
            WithContext("path", r.RequestURI).
            Debug("Request completed")
    })
}

func extractOrGenerateCorrelationID(r *http.Request) string {
    // Try to extract from header
    if correlationId := r.Header.Get("X-Correlation-ID"); correlationId != "" {
        return correlationId
    }
    
    // Generate new ID
    return "req_" + generateUUID()
}

func generateUUID() string {
    // Implementation using crypto/rand or uuid library
    // ...
}

Middleware with Response Header

Go
func correlationIdMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        correlationId := extractOrGenerateCorrelationID(r)
        logs.WithCorrelationID(correlationId).Debug("Request received")
        
        // Add correlation ID to response header
        w.Header().Set("X-Correlation-ID", correlationId)
        
        // Store in context
        ctx := context.WithValue(r.Context(), "correlationId", correlationId)
        r = r.WithContext(ctx)
        
        next.ServeHTTP(w, r)
    })
}

Passing Correlation IDs to Services

Service Layer Integration

Go
type OrderService struct {
    repo       OrderRepository
    paymentSvc PaymentService
}

func (s *OrderService) CreateOrder(ctx context.Context, request *CreateOrderRequest) (*Order, error) {
    // Extract correlation ID from context
    correlationId := ctx.Value("correlationId").(string)
    
    logs.WithCorrelationID(correlationId).
        WithContext("customerId", request.CustomerID).
        Info("Creating order")
    
    // Validate request
    if err := validateOrderRequest(request); err != nil {
        logs.WithCorrelationID(correlationId).
            Error("Order validation failed", err)
        return nil, err
    }
    
    // Create order
    order := &Order{
        ID:         generateID(),
        CustomerID: request.CustomerID,
        CreatedAt:  time.Now(),
    }
    
    // Save order
    err := s.repo.Save(ctx, order)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            Error("Failed to save order", err)
        return nil, err
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", order.ID).
        Info("Order created successfully")
    
    return order, nil
}

Propagating to External Services

Go
func (s *OrderService) CallPaymentService(ctx context.Context, orderId string) error {
    correlationId := ctx.Value("correlationId").(string)
    
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", orderId).
        Debug("Calling payment service")
    
    // Create request with correlation ID header
    req, err := http.NewRequest("POST", "http://payment-service/process", nil)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            Error("Failed to create request", err)
        return err
    }
    
    // Propagate correlation ID
    req.Header.Set("X-Correlation-ID", correlationId)
    req = req.WithContext(ctx)
    
    client := &http.Client{Timeout: 30 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            Error("Payment service request failed", err)
        return err
    }
    defer resp.Body.Close()
    
    logs.WithCorrelationID(correlationId).
        WithContext("status", resp.StatusCode).
        Debug("Payment service response received")
    
    return nil
}

Distributed Tracing Example

Complete Request Flow

Go
// Middleware layer
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    correlationId := extractOrGenerateCorrelationID(r)
    ctx := context.WithValue(r.Context(), "correlationId", correlationId)
    
    logs.WithCorrelationID(correlationId).
        WithContext("method", r.Method).
        WithContext("path", r.URL.Path).
        Debug("Request started")
    
    start := time.Now()
    
    // Route to handler
    h.router.ServeHTTP(w, r.WithContext(ctx))
    
    duration := time.Since(start)
    logs.WithCorrelationID(correlationId).
        WithContext("duration_ms", duration.Milliseconds()).
        Debug("Request completed")
}

// Service layer
func (s *OrderService) ProcessOrder(ctx context.Context, orderId string) error {
    correlationId := ctx.Value("correlationId").(string)
    
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", orderId).
        Debug("Processing order")
    
    // Fetch order
    order, err := s.repo.GetOrder(ctx, orderId)
    if err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("orderId", orderId).
            Error("Failed to fetch order", err)
        return err
    }
    
    // Validate order
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", orderId).
        Trace("Validating order")
    
    if err := validateOrder(order); err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("orderId", orderId).
            Warn("Order validation failed")
        return err
    }
    
    // Process payment
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", orderId).
        Debug("Processing payment")
    
    if err := s.paymentSvc.ProcessPayment(ctx, order); err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("orderId", orderId).
            Error("Payment processing failed", err)
        return err
    }
    
    // Ship order
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", orderId).
        Debug("Shipping order")
    
    if err := s.shippingSvc.ShipOrder(ctx, order); err != nil {
        logs.WithCorrelationID(correlationId).
            WithContext("orderId", orderId).
            Error("Shipping failed", err)
        return err
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("orderId", orderId).
        Info("Order processing completed successfully")
    
    return nil
}

Correlation ID with Goroutines

When spawning goroutines, pass the correlation ID:

Go
func (s *Service) ProcessOrdersAsync(ctx context.Context, orderIds []string) {
    correlationId := ctx.Value("correlationId").(string)
    
    logs.WithCorrelationID(correlationId).
        WithContext("count", len(orderIds)).
        Debug("Spawning goroutines for parallel processing")
    
    var wg sync.WaitGroup
    errChan := make(chan error, len(orderIds))
    
    for _, orderId := range orderIds {
        wg.Add(1)
        
        // Pass context with correlation ID to goroutine
        go func(id string) {
            defer wg.Done()
            
            logs.WithCorrelationID(correlationId).
                WithContext("orderId", id).
                Debug("Goroutine: processing order")
            
            err := s.ProcessOrder(ctx, id)
            if err != nil {
                logs.WithCorrelationID(correlationId).
                    WithContext("orderId", id).
                    Error("Goroutine: processing failed", err)
                errChan <- err
            } else {
                logs.WithCorrelationID(correlationId).
                    WithContext("orderId", id).
                    Debug("Goroutine: processing completed")
                errChan <- nil
            }
        }(orderId)
    }
    
    go func() {
        wg.Wait()
        close(errChan)
    }()
    
    // Collect results
    errorCount := 0
    for err := range errChan {
        if err != nil {
            errorCount++
        }
    }
    
    logs.WithCorrelationID(correlationId).
        WithContext("count", len(orderIds)).
        WithContext("errors", errorCount).
        Info("Parallel processing completed")
}

Best Practices

Correlation ID Best Practices

  • Generate or extract in middleware early in the request pipeline
  • Use consistent header names (e.g., "X-Correlation-ID", "X-Request-ID")
  • Pass through context using context.WithValue() and context.Value()
  • Propagate to external calls - include in HTTP headers for downstream services
  • Pass to goroutines - include correlation ID in context passed to goroutines
  • Use unique formats - include timestamp or random component to ensure uniqueness
  • Log at request start and end for request lifecycle tracking

Context Best Practice

Store the correlation ID in Go's context.Context throughout the request lifecycle. This makes it available to all downstream functions without explicit parameter passing.

Next Steps

Previous
User Identification