0Pricing
Go Academy · Lesson

Logging and Request ID Middleware

Structured logging and trace ID injection

Logging and Request ID Middleware is a free Go Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Go Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

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

Frequently asked questions

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

Yes — the full text of “Logging and Request ID Middleware” is free to read here on the web, and the Go Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Go Academy course, upgrade to CoddyKit PRO.

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; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

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