npx skills add ...
npx skills add cxuu/golang-skills --skill go-logging
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 skills add cxuu/golang-skills --skill go-logging
Compatibility:
log/slogrequires Go 1.21+;testing/slogtestrequires Go 1.22+.
references/LEVELS-AND-CONTEXT.md - Read when choosing log levels, deciding logger-in-context versus explicit parameters, or excluding sensitive fields.references/LOGGING-PATTERNS.md - Read when configuring slog handlers, logging HTTP requests, testing handlers, or migrating from log.Printf.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.
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.
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:
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:
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" attributeAdvisory: 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. Keep the full context-key and middleware implementation in the logging patterns reference so request-scoped logging has one owner.
The handle-once rule belongs to go-error-handling. In logging work, apply it by choosing either a local log-and-recover path or a return path with context, not both for the same error.
Exception: HTTP handlers and other top-of-stack boundaries may log detailed errors server-side while returning a sanitized message to the client:
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.
| 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 |
slog.LogAttrs(ctx, slog.LevelInfo, "request handled",
slog.String("method", r.Method),
slog.Int("status", code),
slog.Duration("elapsed", elapsed),
)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)if err != nil {
slog.Error("checkout failed", "err", err, "user_id", uid)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}