·2 min read·← All posts
Go slog Logging Stdlib

The before state

A typical mature Go codebase:

Five logging implementations; five configuration surfaces; new engineers picking the wrong one.

What slog gives you

Standardised structured logging in the stdlib:

import "log/slog"

// Set up once
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
}))
slog.SetDefault(logger)

// Use everywhere
slog.Info("user logged in", "user_id", id, "method", "passkey")
slog.Error("payment failed", "tx_id", tx, "amount", amt, "error", err)

Output:

{"time":"2026-05-26T14:23:01Z","level":"INFO","msg":"user logged in","user_id":"alice","method":"passkey"}

JSON or text handler. Levels. Structured key/value attributes. Context-aware (slog.InfoContext(ctx, ...) includes trace IDs automatically if you wire it up).

The migration

For Genie, the migration was a regex sweep + a few manual fixes:

# logrus.Info("msg", "key", val)        → slog.Info("msg", "key", val)
# log.Printf("user %s logged in", uid)  → slog.Info("user logged in", "user_id", uid)
# zap.S().Errorw("...", "key", val)     → slog.Error("...", "key", val)

Two patterns took manual work:

Two days for the codebase; ~700 log statements migrated. New PRs use slog naturally; the old libraries got removed in a follow-up PR with no replacements.

What you get back

Fewer dependencies. Five logging libraries gone. go.sum shrinks.

Consistent log shape. Every service emits the same JSON structure. Log aggregators (Loki, Elasticsearch, Datadog) parse uniformly.

slog.Group for nested attributes. When a structured field has sub-fields:

slog.Info("agent dispatched",
    slog.Group("agent",
        "id", agentID,
        "tier", tier,
        "risk", riskClass,
    ),
    "trace_id", traceID,
)

Handler composition. Multiple handlers can chain. A handler that adds the trace ID + a handler that filters by package + a handler that ships to stdout. Each is small; composition is clean.

What slog doesn’t replace

For logs specifically — the stuff you grep for in production — slog is the right tool now. The case for any third-party logging library in a new Go project is weak. The case for migrating an existing project is one good afternoon.

← Back to all posts