Go Academy · Lesson

Logging and Request ID Middleware

Structured logging and trace ID injection

Lesson 2 of 413 steps

Logging and Request ID Middleware is a free Go Academy lesson on CoddyKit. This is lesson 2 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Go Academy learning path, and your progress syncs across the web and the CoddyKit app. The Go Academy course includes 4 lessons in total.

Why structured logging?

Plain text logs are hard to parse at scale. Structured logging (JSON or key=value) lets log aggregators (Datadog, Loki, ELK) filter and correlate entries by field.

Request ID generation

Generate a unique ID per request (UUID or ULID) in middleware and attach it to the response header and context:

func RequestID(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        id := r.Header.Get("X-Request-ID")
        if id == "" { id = uuid.New().String() }
        w.Header().Set("X-Request-ID", id)
        ctx := context.WithValue(r.Context(), reqIDKey{}, id)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Logging middleware with slog

Go 1.21 added log/slog for structured logging. Use it inside middleware:

func StructuredLogger(logger *slog.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            cw := &captureWriter{ResponseWriter: w}
            next.ServeHTTP(cw, r)
            logger.Info("request",
                "method", r.Method,
                "path", r.URL.Path,
                "status", cw.status,
                "latency_ms", time.Since(start).Milliseconds(),
                "request_id", r.Context().Value(reqIDKey{}),
            )
        })
    }
}

captureWriter

Wrap the ResponseWriter to capture the status code written by the handler:

type captureWriter struct {
    http.ResponseWriter
    status int
}
func (cw *captureWriter) WriteHeader(code int) {
    cw.status = code
    cw.ResponseWriter.WriteHeader(code)
}
func (cw *captureWriter) Status() int {
    if cw.status == 0 { return http.StatusOK }
    return cw.status
}

Adding request ID to every log line

Build a child logger per request with the ID pre-populated, then store it in the context:

reqLogger := logger.With("request_id", id)
ctx := context.WithValue(r.Context(), loggerKey{}, reqLogger)

Retrieving the logger from context

Helper to get the request-scoped logger from any handler or service:

func LoggerFromContext(ctx context.Context) *slog.Logger {
    l, ok := ctx.Value(loggerKey{}).(*slog.Logger)
    if !ok { return slog.Default() }
    return l
}

Log levels

Use appropriate levels: Debug for development verbosity, Info for request summaries, Warn for recoverable anomalies, Error for actionable failures. Do not log sensitive fields (tokens, passwords).

Latency buckets

Include request latency in every log line. Aggregate into histogram buckets in your metrics system (Prometheus) for P50/P95/P99 percentile tracking.

Correlation IDs in microservices

Forward the X-Request-ID header from incoming requests to all outgoing HTTP and gRPC calls to enable distributed tracing without a full tracing system.

Log sampling

For high-traffic services, sample debug/info logs (e.g., log 1 in 100 requests at debug level) to reduce storage costs while keeping error logs complete.

zap vs slog

zap (Uber) is faster than slog for high-throughput services but requires more setup. slog is standard library, portable, and sufficient for most services. Prefer slog unless benchmarks show a bottleneck.

Quick Check

Why is a captureWriter needed in logging middleware?

Recap: Logging and Request ID

Key points:

  • Generate/forward X-Request-ID in middleware; store in context
  • Use slog for structured logs with key-value fields
  • captureWriter intercepts status code for post-handler logging
  • Forward correlation ID to all downstream calls
Free to start

Learn Go with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
51
Lessons
203

Frequently Asked Questions

Is the “Logging and Request ID Middleware” lesson free?

Yes — any 3 lessons of the Go Academy learning path, including “Logging and Request ID Middleware”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Go Academy course includes 4 lessons in total.

What will I learn in “Logging and Request ID Middleware”?

Structured logging and trace ID injection You practise Go Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Go Academy?

No prior experience is required. Go Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

How long does the “Logging and Request ID Middleware” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Go Academy lesson?

Yes. Every Go Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. HTTP Middleware Chain Fundamentals
  2. Logging and Request ID Middleware
  3. Auth and Rate Limiting Middleware
  4. CORS and Panic Recovery Middleware
← Back to Go Academy