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")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
// ...
}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)
})
}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
}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
}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
}// 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")
}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()andcontext.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.