The Problem#
3 AM, alarm sounds: order creation failed. You open the log system and face:
[ServiceA] 10:23:45 User requested order
[ServiceB] 10:23:46 Inventory check succeeded
[ServiceC] 10:23:46 Payment call failed
[ServiceA] 10:23:47 Order failed
[ServiceB] 10:23:47 Another user's request...
[ServiceC] 10:23:47 Yet another user's request...Thousands of logs interleaved together. Which ones belong to this user’s request? This is the core problem distributed tracing solves.
What is Request ID?#
Request ID is a unique identifier assigned to each request, persisting throughout its entire lifecycle:
User Request ──▶ ServiceA ──▶ ServiceB ──▶ ServiceC ──▶ Database
│ │ │ │
└────────────┴───────────┴───────────┘
Same Request IDAnalogy: Like a tracking number for packages - no matter how many transfer stations it passes through, you can trace the complete path with the number.
Design Principles#
1. Uniqueness#
Ensure global uniqueness to avoid collisions:
// Recommended: UUID v4
requestID := uuid.New().String() // "550e8400-e29b-41d4-a716-446655440000"
// Or: Snowflake algorithm (ordered + unique)
requestID := snowflake.Generate() // "1234567890123456789"
// Not recommended: Timestamp (collisions under high concurrency)
requestID := time.Now().UnixNano() // Dangerous!2. Propagation#
Request ID must be automatically passed between services:
┌─────────┐ ┌─────────┐ ┌─────────┐
│Service A│────▶│Service B│────▶│Service C│
│X-Req-ID │ │X-Req-ID │ │X-Req-ID │
│=abc123 │ │=abc123 │ │=abc123 │
└─────────┘ └─────────┘ └─────────┘HTTP Approach:
// Service A: Generate and pass
func handler(w http.ResponseWriter, r *http.Request) {
requestID := uuid.New().String()
r.Header.Set("X-Request-ID", requestID)
// Call downstream services...
}
// Service B: Receive and continue passing
func handler(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
// Log...
// Continue passing when calling downstream...
}gRPC Approach:
// Use metadata for propagation
md := metadata.Pairs("x-request-id", requestID)
ctx := metadata.NewOutgoingContext(context.Background(), md)3. Generation Timing#
| Scenario | Generation Location |
|---|---|
| User request | Gateway layer (API Gateway) |
| Scheduled task | At task start |
| Message consumption | Generated at production, propagated at consumption |
| Internal calls | Use upstream Request ID |
Implementation Approaches#
Approach 1: Gateway Unified Generation#
User ──▶ API Gateway ──▶ ServiceA ──▶ ServiceB
│
▼
Generate Request ID
│
└──────────┴──────────▶ All services use the same ID# Nginx configuration
location / {
set $request_id $request_id; # Use Nginx built-in variable
proxy_set_header X-Request-ID $request_id;
proxy_pass http://backend;
}Approach 2: Middleware Auto-Handling#
// Gin middleware example
func RequestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Prefer upstream-provided ID
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
// Store in context for later use
c.Set("requestID", requestID)
// Return in response header for debugging
c.Header("X-Request-ID", requestID)
c.Next()
}
}Approach 3: Logger Integration#
// Use structured logging
log := logger.WithField("request_id", requestID)
// All logs automatically carry request_id
log.Info("Query inventory") // {"level":"info","request_id":"abc123","msg":"Query inventory"}
log.Error("Payment failed") // {"level":"error","request_id":"abc123","msg":"Payment failed"}Best Practices#
1. Naming Convention#
Commonly used header names:
X-Request-ID # Most common in industry
X-Trace-ID # Tracing systems
X-Correlation-ID # Correlation ID2. Log Format#
Use JSON format uniformly for easy log system searching:
{
"timestamp": "2026-03-19T10:23:45.123Z",
"level": "ERROR",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"service": "order-service",
"msg": "Payment call failed",
"error": "connection timeout"
}3. Tracing Integration#
Request ID can work with OpenTelemetry and other tracing systems:
Request ID (business layer) ──▶ User-readable, log search
Trace ID (tracing layer) ──▶ System-generated, chain visualization// Link Request ID and Trace ID
span := trace.SpanFromContext(ctx)
span.SetAttributes(
attribute.String("request.id", requestID),
)4. Async Scenario Handling#
Message Queue:
// Producer
msg := kafka.Message{
Headers: map[string]string{
"X-Request-ID": requestID,
},
Value: orderData,
}
// Consumer
requestID := msg.Headers["X-Request-ID"]
ctx := context.WithValue(context.Background(), "requestID", requestID)Async Tasks:
// Pass Request ID to goroutine
go func(requestID string) {
log := logger.WithField("request_id", requestID)
// Async processing...
}(requestID)Real-World Example#
Problem Location#
With Request ID, troubleshooting becomes simple:
# ELK query
request_id:"550e8400-e29b-41d4-a716-446655440000"
# Result: Sorted by time, complete chain at a glance
[10:23:45] ServiceA Received order request
[10:23:45] ServiceA Called inventory service
[10:23:46] ServiceB Inventory check succeeded
[10:23:46] ServiceA Called payment service
[10:23:46] ServiceC Payment call failed: connection timeout
[10:23:47] ServiceA Order failedComplete Example#
package main
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
var logger *zap.Logger
func main() {
r := gin.New()
// Request ID middleware
r.Use(func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
c.Set("requestID", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
})
// Logging middleware
r.Use(func(c *gin.Context) {
requestID, _ := c.Get("requestID")
logger.Info("Request started",
zap.String("request_id", requestID.(string)),
zap.String("path", c.Request.URL.Path),
)
c.Next()
})
r.POST("/order", createOrder)
r.Run(":8080")
}
func createOrder(c *gin.Context) {
requestID, _ := c.Get("requestID")
// Pass Request ID when calling downstream services
// req.Header.Set("X-Request-ID", requestID.(string))
logger.Info("Creating order",
zap.String("request_id", requestID.(string)),
)
c.JSON(200, gin.H{
"request_id": requestID,
"status": "success",
})
}Summary#
| Key Point | Description |
|---|---|
| Uniqueness | UUID or Snowflake, avoid collisions |
| Propagation | HTTP Header / gRPC Metadata auto-pass |
| Generation Location | Gateway unified generation, downstream propagation |
| Logger Integration | Structured logging, auto-carry Request ID |
| Async Handling | Message header / Context propagation |
Request ID is the cornerstone of distributed system observability. Small investment, big return - one thread ties the entire call chain together.
