go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Use when choosing a Go logger, configuring slog, writing structured log statements, picking log levels, or attaching request-scoped fields. Apply proactively whenever code calls log/fmt to emit operational information, migrates off log/logrus/zap/zerolog, or sets up production
Claude Code
$ npx -y skills add muratmirgun/gophers --skill go-logging --agent claude-code
Codex
$ npx skills add muratmirgun/gophers --skill go-logging -a codex -y
Cursor
$ npx skills add muratmirgun/gophers --skill go-logging -a cursor -y
Gemini CLI
$ npx skills add muratmirgun/gophers --skill go-logging -a gemini-cli -y
GitHub Copilot
$ npx skills add muratmirgun/gophers --skill go-logging -a github-copilot -y
OpenCode
$ npx skills add muratmirgun/gophers --skill go-logging -a opencode -y
Windsurf
$ npx skills add muratmirgun/gophers --skill go-logging -a windsurf -y
name: go-logging description: "Use when choosing a Go logger, configuring slog, writing structured log statements, picking log levels, or attaching request-scoped fields. Apply proactively whenever code calls log/fmt to emit operational information, migrates off log/logrus/zap/zerolog, or sets up production logging. Covers structured logging only — metrics, traces, profiling, and RUM belong to a separate observability skill." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. Requires Go 1.21+ for log/slog. Go 1.26 slog.NewMultiHandler is noted where relevant." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
Logs are written for **operators** — the human who will be paged at 3 a.m. and needs to know what happened. Every log line either helps diagnose a production issue or it is noise. `log/slog` from the standard library is the default; reach for anything else only after measuring.
> This skill covers **logging only**. Metrics, distributed tracing, profiling, and RUM are a separate concern — they belong to a future `go-observability` skill. Do not confuse them with logging here.
1. **Use `log/slog`** for new code. Structured, leveled, in the standard library since Go 1.21. 2. **Static message, structured fields.** The message describes what happened; data goes in key-value attributes. 3. **Log or return, never both.** Logging a wrapped error makes the same failure appear at every layer. 4. **Log at the boundary.** HTTP handlers, job runners, and `main` log. Library code wraps and returns. 5. **Use snake_case keys** consistently across the codebase (`user_id`, `request_id`, `elapsed_ms`). 6. **`slog.Error` always carries an `"err"` attribute.** Without it, you logged a sentence, not an error. 7. **Never log secrets, PII, or unbounded data.** Tokens, full credit cards, request bodies — none of it.
| Situation | Use | |---|---| | New production service | `log/slog` | | Trivial CLI / one-off script | `log` (the standard package) | | Measured hot-path bottleneck where slog dominates the flame graph | `zap` or `zerolog`, but keep the structured style | | Existing zap/logrus/zerolog code | Migrate to `slog` with a bridge handler; see [references/slog-handler-ecosystem.md](references/slog-handler-ecosystem.md) |
`slog`'s API is stable, the ecosystem has consolidated around it, and JSON output works with every log shipper. Do not introduce a third-party logger without a benchmark showing the win.
Build log messages from a **static message** plus typed fields:
// Good — static message, structured fields
slog.Info("order placed", "order_id", orderID, "total_cents", totalCents)
// Bad — dynamic data baked into the message string
slog.Info(fmt.Sprintf("order %d placed for $%.2f", orderID, total))The aggregator (Loki, Elastic, CloudWatch) can index `order_id`. It cannot index a sprintf'd sentence.
For hot paths, typed constructors avoid allocations:
slog.LogAttrs(ctx, slog.LevelInfo, "request handled",
slog.String("method", r.Method),
slog.Int("status", code),
slog.Duration("elapsed", elapsed),
)| Level | When | Default | |---|---|---| | `Debug` | Developer-only diagnostics; tracing internal state | Disabled in prod | | `Info` | Notable lifecycle events: startup, shutdown, config loaded | Enabled | | `Warn` | Unexpected but recoverable: retry succeeded, deprecated flag used | Enabled | | `Error` | Operation failed; someone should look | Enabled |
Rules of thumb:
slog.Error("payment failed", "err", err, "order_id", id)
slog.Warn("retry succeeded", "attempt", n, "endpoint", url)
slog.Info("server started", "addr", addr)
slog.Debug("cache lookup", "key", key, "hit", hit)> Read [references/levels-and-context.md](references/levels-and-context.md) when choosing between `Warn` and `Error`, defining custom verbosity levels, or pre-checking `Enabled()` on hot paths.
Derive a logger per request that carries the fields every downstream call should include:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log := slog.With("request_id", requestID(r))
ctx := context.WithValue(r.Context(), loggerKey{}, log)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func FromContext(ctx context.Context) *slog.Logger {
if l, ok := ctx.Value(loggerKey{}).(*slog.Logger); ok { return l }
return slog.Default()
}Use the `Context`-aware variants (`slog.InfoContext`, `slog.ErrorContext`) so handlers that read trace IDs from the context can stamp them into the record:
slog.InfoContext(ctx, "order placed", "order_id", id)
> Read [references/request-scope-and-middleware.md](references/request-scope-and-middleware.md) when wiring request IDs, building logging middleware, or choosing between context-stored loggers and explicit parameters.
Logging an error and then returning it produces the same failure at every layer, and three log records for one bug:
// Bad — every caller up the stack logs it again
if err != nil {
slog.Error("query failed", "err", err)
return fmt.Errorf("query: %w", err)
}
// Good — wrap and return; the boundary logs once
if err != nil {
return fmt.Errorf("loading user %d: %w", id, err)
}The **only** layer that logs is the one that finishes the work: the HTTP handler, the job runner, `main`. That layer may log a detailed record server-side while returning a sanitised message to the client:
if err := checkout(ctx); err != nil {
slog.ErrorContext(ctx, "checkout failed", "err", err, "user_id", uid)
http26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.