Hone logo
Hone
Problems

Implementing Request IDs in Go for Distributed Systems

In modern microservice architectures, tracing requests across multiple services is crucial for debugging and monitoring. A common technique to achieve this is by generating and propagating a unique Request ID for each incoming request. This challenge focuses on implementing a robust mechanism for generating and managing these Request IDs within a Go application.

Problem Description

Your task is to design and implement a system in Go that generates a unique Request ID for each incoming HTTP request and makes it accessible throughout the request's lifecycle, including any downstream calls. This Request ID should be a string that is both unique and reasonably short.

Key Requirements:

  • Generate Unique IDs: Implement a function or mechanism to generate a new, unique Request ID for every incoming HTTP request.
  • Context Propagation: The generated Request ID must be accessible in any Go function or goroutine that is part of handling that specific request. This typically involves using context.Context.
  • HTTP Middleware: The Request ID generation and insertion into the context should be handled by an HTTP middleware.
  • ID Format: The Request ID should be a string. A common format is a UUID, but a simpler, unique identifier can also be sufficient for this challenge.
  • Logging Integration (Optional but Recommended): Demonstrate how the Request ID can be easily included in log messages for better traceability.

Expected Behavior:

When an HTTP request arrives, the middleware should:

  1. Generate a new Request ID.
  2. Add this Request ID to the context.Context of the request.
  3. Pass the request (with the context) to the next handler.
  4. Any downstream functions called within the handler should be able to retrieve the Request ID from the context.

Edge Cases:

  • Non-HTTP Requests: While this challenge focuses on HTTP, consider if your approach could be extended to other request types.
  • Concurrent Requests: The ID generation mechanism must be safe for concurrent use.

Examples

Example 1: Basic HTTP Request Handling

Input: An HTTP GET request to "/resource"

Output:
- A unique Request ID (e.g., "a1b2c3d4-e5f6-7890-1234-567890abcdef") is generated.
- The handler function receives a context containing this Request ID.
- The handler logs a message including the Request ID.

HTTP Request:
GET /resource HTTP/1.1
Host: localhost:8080

Handler Logic:
func handler(w http.ResponseWriter, r *http.Request) {
    requestID, ok := GetRequestID(r.Context()) // Assume GetRequestID retrieves from context
    if !ok {
        requestID = "unknown"
    }
    log.Printf("Handling request with ID: %s", requestID)
    w.Write([]byte("Processed request with ID: " + requestID))
}

Expected Log Output:
[timestamp] Handling request with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef

Expected HTTP Response:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 47

Processed request with ID: a1b2c3d4-e5f6-7890-1234-567890abcdef

Example 2: Request ID Propagation to Downstream Function

Input: An HTTP POST request to "/process" that triggers a downstream function.

Output:
- A unique Request ID is generated.
- The downstream function can access and use this Request ID.

HTTP Request:
POST /process HTTP/1.1
Host: localhost:8080
Content-Type: application/json

Handler Logic:
func handler(w http.ResponseWriter, r *http.Request) {
    requestID, _ := GetRequestID(r.Context())
    log.Printf("Main handler received request with ID: %s", requestID)

    // Simulate a downstream operation
    err := performDownstreamOperation(r.Context(), "some_data")
    if err != nil {
        http.Error(w, "Downstream operation failed", http.StatusInternalServerError)
        return
    }
    w.Write([]byte("Downstream operation successful for ID: " + requestID))
}

func performDownstreamOperation(ctx context.Context, data string) error {
    requestID, ok := GetRequestID(ctx) // Retrieve from context
    if !ok {
        requestID = "unknown_downstream"
    }
    log.Printf("Downstream operation processing for ID: %s", requestID)
    // ... perform actual operation ...
    return nil
}

Expected Log Output (order might vary slightly due to goroutines):
[timestamp] Main handler received request with ID: ffedcba0-9876-5432-10fe-dcba09876543
[timestamp] Downstream operation processing for ID: ffedcba0-9876-5432-10fe-dcba09876543

Expected HTTP Response:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 59

Downstream operation successful for ID: ffedcba0-9876-5432-10fe-dcba09876543

Constraints

  • The Go application must use the standard net/http package for handling HTTP requests.
  • The Request ID generation mechanism must be thread-safe.
  • The maximum length of the generated Request ID string should be reasonable (e.g., less than 40 characters).
  • The solution should aim for efficiency, with minimal overhead per request.

Notes

  • Consider using the context package for propagating the Request ID.
  • For generating unique IDs, the github.com/google/uuid package is a good option, but you can also implement a simpler, custom generator if preferred.
  • Think about how you would integrate this with a logging library like log/slog or logrus.
  • The core of this challenge is the middleware and context propagation. The actual handler logic can be very simple.
Loading editor...
go