Skip to main content

Log Structure & Context

What goes in a log line and how to make sure the right fields are there without having to remember.

Structured logging

  • Consistent shape, typically key-value pairs
  • Doesn't have to be JSON, but JSON is the standard (ELK, Loki, CloudWatch parse it natively)
  • In Go, slog is structured by default. Every attribute (slog.String, slog.Int, slog.Duration) becomes a typed key

Slog groups

Group related attributes under one key. Produces nested JSON or dotted text keys, both queryable.

logger.Info("user logged in",
    slog.Group("artist",
        slog.String("name", "Thebe Kgositsile"),
        slog.String("stage_name", "Earl Sweatshirt"),
        slog.String("group", "Odd Future"),
    ),
)

Use it for errors. Group message, type, and stack trace under one error key. In linko: internal/logging/logger.go:89-102, every error log is grouped into {message, stack_trace, ...extra_attrs}.

What context every log needs

  • Machine, host, container, pod
  • Request, request ID
  • User, when authenticated
  • Service, which service in the monorepo

Propagating context

  • Intra-process, context.Context for request-scoped values, request-scoped *slog.Logger derived from the root via .With(...). In linko: middleware.go:20 and middleware.go:30-33 define a LogContext struct that travels on the request context.
  • Inter-process, propagate a request ID across services via headers. In linko: middleware.go:51-60, generates or accepts X-Request-ID, sets it on the response.

See Correlated logging for the full pattern, including Sentry, trace IDs, and frontend/backend join.

Instance context

Bind once at startup, attach to every log: env, hostname, git_sha + build_time (see Foundations for the binding pattern). In linko: main.go:63-68.

For desktop/mobile: also OS, time zone, library versions.

Request logging middleware

The cleanest way to log requests in an HTTP service. One middleware that:

  • Records start time
  • Wraps the response writer to capture status + bytes
  • Calls next.ServeHTTP
  • Emits one structured Served request log with method, path, status, duration, request_id, user (if any), error (if any)

In linko: middleware.go:118-150. IP redaction (middleware.go:160-171) means /24 only, no full client IP.

Quick find

Search the garden