skills/go-logging/SKILL.md
Use when choosing a logging approach, configuring slog, writing structured log statements, or deciding log levels in Go. Also use when setting up production logging, adding request-scoped context to logs, or migrating from log to slog, even if the user doesn't explicitly mention logging. Does not cover error handling strategy (see go-error-handling).
npx skillsauth add cxuu/golang-skills go-loggingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Logs are for operators, not developers. Every log line should help someone diagnose a production issue. If it doesn't serve that purpose, it's noise.
Normative: Use
log/slogfor new Go code.
slog is structured, leveled, and in the standard library (Go 1.21+). It
covers the vast majority of production logging needs.
Which logger?
├─ New production code → log/slog
├─ Trivial CLI / one-off → log (standard)
└─ Measured perf bottleneck → zerolog or zap (benchmark first)
Do not introduce a third-party logging library unless profiling shows slog
is a bottleneck in your hot path. When you do, keep the same structured
key-value style.
Read references/LOGGING-PATTERNS.md when setting up slog handlers, configuring JSON/text output, or migrating from log.Printf to slog.
Normative: Always use key-value pairs. Never interpolate values into the message string.
The message is a static description of what happened. Dynamic data goes in key-value attributes:
// Good: static message, structured fields
slog.Info("order placed", "order_id", orderID, "total", total)
// Bad: dynamic data baked into the message string
slog.Info(fmt.Sprintf("order %d placed for $%.2f", orderID, total))
Advisory: Use
snake_casefor log attribute keys.
Keys should be lowercase, underscore-separated, and consistent across the
codebase: user_id, request_id, elapsed_ms.
For performance-critical paths, use typed constructors to avoid allocations:
slog.LogAttrs(ctx, slog.LevelInfo, "request handled",
slog.String("method", r.Method),
slog.Int("status", code),
slog.Duration("elapsed", elapsed),
)
Read references/LEVELS-AND-CONTEXT.md when optimizing log performance or pre-checking with Enabled().
Advisory: Follow these level semantics consistently.
| Level | When to use | Production default | |-------|-------------|--------------------| | Debug | Developer-only diagnostics, tracing internal state | Disabled | | Info | Notable lifecycle events: startup, shutdown, config loaded | Enabled | | Warn | Unexpected but recoverable: deprecated feature used, retry succeeded | Enabled | | Error | Operation failed, requires operator attention | Enabled |
Rules of thumb:
slog.Error should always include an "err" attributeslog.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 when choosing between Warn and Error or defining custom verbosity levels.
Advisory: Derive loggers from context to carry request-scoped fields.
Use middleware to enrich a logger with request ID, user ID, or trace ID, then pass the enriched logger downstream via context or as an explicit parameter:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger := slog.With("request_id", requestID(r))
ctx := context.WithValue(r.Context(), loggerKey, logger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
All subsequent log calls in that request carry request_id automatically.
Read references/LOGGING-PATTERNS.md when implementing logging middleware or passing loggers through context.
Normative: Handle each error exactly once — either log it or return it.
Logging an error and then returning it causes duplicate noise as callers up the stack also handle the error.
// Bad: logged here AND by every caller up the stack
if err != nil {
slog.Error("query failed", "err", err)
return fmt.Errorf("query: %w", err)
}
// Good: wrap and return — let the caller decide
if err != nil {
return fmt.Errorf("query: %w", err)
}
Exception: HTTP handlers and other top-of-stack boundaries may log detailed errors server-side while returning a sanitized message to the client:
if err != nil {
slog.Error("checkout failed", "err", err, "user_id", uid)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
See go-error-handling for the full handle-once pattern and error wrapping guidance.
Normative: Never log secrets, credentials, PII, or high-cardinality unbounded data.
Read references/LEVELS-AND-CONTEXT.md when deciding what data is safe to include in log attributes.
| Do | Don't |
|----|-------|
| slog.Info("msg", "key", val) | log.Printf("msg %v", val) |
| Static message + structured fields | fmt.Sprintf in message |
| snake_case keys | camelCase or inconsistent keys |
| Log OR return errors | Log AND return the same error |
| Derive logger from context | Create a new logger per call |
| Use slog.Error with "err" attr | slog.Info for errors |
| Pre-check Enabled() on hot paths | Always allocate log args |
tools
Use when writing, reviewing, or improving Go test code — including table-driven tests, subtests, parallel tests, test helpers, test doubles, and assertions with cmp.Diff. Also use when a user asks to write a test for a Go function, even if they don't mention specific patterns like table-driven tests or subtests. Does not cover benchmark performance testing (see go-performance).
development
Use when working with Go formatting, line length, nesting, naked returns, semicolons, or core style principles. Also use when a style question isn't covered by a more specific skill, even if the user doesn't reference a specific style rule. Does not cover domain-specific patterns like error handling, naming, or testing (see specialized skills). Acts as fallback when no more specific style skill applies.
development
Use when optimizing Go code, investigating slow performance, or writing performance-critical sections. Also use when a user mentions slow Go code, string concatenation in loops, or asks about benchmarking, even if the user doesn't explicitly mention performance patterns. Does not cover concurrent performance patterns (see go-concurrency).
development
Use when creating Go packages, organizing imports, managing dependencies, or deciding how to structure Go code into packages. Also use when starting a new Go project or splitting a growing codebase into packages, even if the user doesn't explicitly ask about package organization. Does not cover naming individual identifiers (see go-naming).