From golang-skills
Guides Go logging with slog: choosing loggers, writing structured key-value statements, setting levels, production configuration, request context, and migrating from log.
npx claudepluginhub cxuu/golang-skills --plugin golang-skillsThis skill uses the workspace's default tool permissions.
Logs are for **operators**, not developers. Every log line should help someone
Sets up structured logging pipelines in Go using samber/slog-* packages for multi-handlers (slog-multi), sampling (slog-sampling), formatting (slog-formatter), HTTP middleware (fiber/gin/chi/echo), and backend sinks (datadog/sentry/loki/syslog). Use when adopting slog or importing samber/slog-*
Implements structured logging with slog, OpenTelemetry tracing and metrics, Prometheus metrics, and health checks for Go services.
Implements structured logging with levels, request context, PII sanitization using Winston in Node.js. Covers Python structlog, Go zap, ELK/CloudWatch integration for log bloat, PII exposure, missing context.
Share bugs, ideas, or general feedback.
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 |