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…

INSTALLATION
npx skills add https://github.com/cxuu/golang-skills --skill go-logging
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Go Logging

Core Principle

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.

Choosing a Logger

Normative: Use log/slog for 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.

Structured Logging

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))

Key Naming

Advisory: Use snake_case for log attribute keys.

Keys should be lowercase, underscore-separated, and consistent across the

codebase: user_id, request_id, elapsed_ms.

Typed Attributes

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().

Log Levels

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:

  • If nobody should act on it, it's not Error — use Warn or Info
  • If it's only useful with a debugger attached, it's Debug
  • slog.Error should always include an "err" attribute
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 when choosing between Warn and Error or defining custom verbosity levels.

Request-Scoped Logging

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.

Log or Return, Not Both

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.

What NOT to Log

Normative: Never log secrets, credentials, PII, or high-cardinality unbounded data.

  • Passwords, API keys, tokens, session IDs
  • Full credit card numbers, SSNs
  • Request/response bodies that may contain user data
  • Entire slices or maps of unbounded size

Read references/LEVELS-AND-CONTEXT.md when deciding what data is safe to include in log attributes.

Quick Reference

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

Related Skills

  • Error handling: See go-error-handling when deciding whether to log or return an error, or for the handle-once pattern
  • Context propagation: See go-context when passing request-scoped values (including loggers) through context
  • Performance: See go-performance when optimizing hot-path logging or reducing allocations in log calls
  • Code review: See go-code-review when reviewing logging practices in Go PRs
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card