go 29 lines · 1 tab

Request-scoped slog logger with JSON output (Go 1.21+)

Leah Thompson Jan 2026
1 tab
package observability

import (
  "context"
  "log/slog"
  "os"
)

type ctxKey string

const slogKey ctxKey = "slog"

func Base() *slog.Logger {
  h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
  return slog.New(h)
}

func With(ctx context.Context, l *slog.Logger) context.Context {
  return context.WithValue(ctx, slogKey, l)
}

func From(ctx context.Context) *slog.Logger {
  if v := ctx.Value(slogKey); v != nil {
    if l, ok := v.(*slog.Logger); ok {
      return l
    }
  }
  return slog.New(slog.NewTextHandler(os.Stdout, nil))
}
1 file · go Explain with highlit

I’ve started using log/slog for services that want structured logs without a heavy dependency. The key is treating the request logger as data: create a base logger with JSON output, then derive a request logger with fields like request_id and path. I avoid logging raw request bodies by default and instead log stable identifiers and error codes. Because slog uses context.Context, it’s easy to pass the logger down without changing every function signature. In production, this makes logs far more useful for queries like “show all errors for request_id=...” and it keeps log formatting consistent across packages. It’s a small wrapper, but it makes the whole codebase feel more coherent.


Related snips

Share this code

Here's the card — post it anywhere.

Request-scoped slog logger with JSON output (Go 1.21+) — share card
Link copied