From skills
Go structured logging using Zap Sugar API with context propagation. Use when initializing loggers, writing log statements, extracting request context into logs, configuring log output, or deciding which layer should log. Apply whenever user writes log.Infow/Errorw/Warnw calls, uses zap fields, or asks about request-ID propagation in Go projects.
How this skill is triggered — by the user, by Claude, or both
Slash command
/skills:golang-loggingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
```go
// Package-level functions — use when no request context is available
log.Debugw("Starting server", "addr", cfg.Addr, "mode", cfg.ServerMode)
log.Infow("Server started", "addr", ":8080")
log.Warnw("Config missing", "key", "timeout", "default", 30)
log.Errorw("Failed to connect", "err", err, "host", host)
// With request context — use inside handlers, biz, store
log.W(ctx).Infow("Healthz handler called", "method", "Healthz", "status", "healthy")
log.W(ctx).Errorw("Failed to compare password", "err", err)
log.W(ctx).Debugw("Got users from storage", "count", len(users))
log.W(ctx) automatically extracts X-Request-ID and X-User-ID from context and adds them to every log line — do not add these fields manually.
// ✓ error field: string key "err"
log.W(ctx).Errorw("Failed to sign token", "err", err)
// ✓ domain fields: lowercase snake_case keys
log.W(ctx).Errorw("Failed to add role", "user", userID, "role", roleName)
// ✗ don't use zap typed fields (zap.String, zap.Error) — breaks Sugar API
log.W(ctx).Errorw("msg", zap.Error(err)) // ✗
log.W(ctx).Errorw("msg", zap.String("k", v)) // ✗
// ✗ don't format into the message string
log.W(ctx).Errorw(fmt.Sprintf("Failed for user %s", uid)) // ✗ use KV instead
log.W(ctx).Errorw("Failed for user", "user", uid) // ✓
| Layer | Log? | What |
|---|---|---|
| handler | No | core.HandleJSONRequest handles it |
| biz | Yes — on internal failures | raw technical error + context fields |
| biz | No — on user errors | just return errno.ErrXxx |
| store | No | just return errno.ErrDBRead.WithMessage(err.Error()) |
| server init | Yes | startup/shutdown events, config values |
// biz: log raw err, return sanitized errno
if err := authn.Compare(hash, password); err != nil {
log.W(ctx).Errorw("Failed to compare password", "err", err)
return nil, errno.ErrPasswordInvalid
}
// server init: no ctx, use package-level
log.Infow("Initializing DB", "type", "mysql", "addr", cfg.MySQLOptions.Addr)
Call log.Init() once in cmd/<app>/app/server.go, before any other code:
func run(opts *options.ServerOptions) error {
log.Init(logOptions())
defer log.Sync() // flush on exit
// ...
}
func logOptions() *log.Options {
opts := log.NewOptions()
if viper.IsSet("log.level") {
opts.Level = viper.GetString("log.level")
}
if viper.IsSet("log.format") {
opts.Format = viper.GetString("log.format")
}
if viper.IsSet("log.output-paths") {
opts.OutputPaths = viper.GetStringSlice("log.output-paths")
}
return opts
}
log:
disable-caller: false
disable-stacktrace: false
level: info # debug | info | warn | error
format: json # json | console
output-paths:
- stdout
&log.Options{
Level: "info", // default
Format: "console", // default
DisableCaller: false, // show file:line in every log entry
DisableStacktrace: false, // print stack on panic+
OutputPaths: []string{"stdout"},
}
fmt.Sprintf in message: use key-value pairs insteadzap.String()/zap.Error() typed fields with Sugar APIcore.HandleJSONRequest handles itreturn err (raw error) — log raw, return errno sentinellog.Init() inside a package init() — call only from cmd/ startupProvides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.
npx claudepluginhub shipengqi/skills --plugin database-redis