golang-slog
- Repo stars 0
- Author updated Live
- Author repo skills-registry
- Domain
- AI
- Compatible agents
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- Trust score
- 88 / 100 · community maintained
- Author / version / license
- @tomevault-io · no license declared
- Token usage
- Moderate
- Setup complexity
- Guided setup
- External API key
- Not required
- Operating systems
- Unspecified (assume cross-platform)
- Runtime requirements
- No special requirements
- Permissions
-
- Read-only
- Write / modify
- Env read
- Network behavior
- External requests
- Install commands
- 26 variants
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
---
name: golang-slog
description: Implement structured logging in Go with log/slog, including logger setup, common attributes, con…
category: ai
runtime: no special runtime
---
# golang-slog output preview
## PART A: Task fit
- Use case: Implement structured logging in Go with log/slog, including logger setup, common attributes, context propagation, redaction, and performance-minded patterns. Use when this capability is needed..
- Inputs: target material, constraints, expected output, and acceptance criteria.
- Evidence boundary: follow “When to Use / Default Stance / Version Notes” and do not present inference as author intent.
## PART B: Execution result
- **01** The card summarizes the use case; runtime output centers on “Implement structured logging in Go with log/slog, including logger setup, common attributes, context propagation, redaction, and performance-minded patterns. Use when this capability is needed.”.
- **02** When the source has headings, the agent prioritizes “When to Use / Default Stance / Version Notes” so the result follows the author’s structure.
- **03** Typical output includes task judgment, concrete steps, required commands or file edits, validation, and follow-up options.
- **04** Risk context follows the fingerprint: read files, write/modify files, read environment variables; may access external network resources; usually needs no extra API key.
## Running Rules
- read files, write/modify files, read environment variables; may access external network resources; usually needs no extra API key.
- Validate with a small sample before expanding scope.
- Return the result, validation criteria, and next iteration options. The source does not require a stable slash command. After installation, invoke the skill by name and describe the task.
Name target files or source material, expected output, forbidden changes, and whether network or shell access is allowed. Permission fingerprint: read files, write/modify files, read environment variables.
Start with a small task and check whether the result follows “When to Use / Default Stance / Version Notes”. Inspect diffs, logs, previews, or tests before expanding scope.
Confirm the final output includes a concrete result, evidence, and next action. If it stays generic, tighten inputs, boundaries, and acceptance criteria.
---
name: golang-slog
description: Implement structured logging in Go with log/slog, including logger setup, common attributes, con…
category: ai
source: tomevault-io/skills-registry
---
# golang-slog
## When to use
- Implement structured logging in Go with log/slog, including logger setup, common attributes, context propagation, reda…
- Use it when the task has clear inputs, repeatable steps, and validation criteria.
## What to provide
- Target material, scope, expected result, and forbidden changes.
- Whether network, commands, file writes, or external services are allowed.
## Execution rules
- Organize steps around “When to Use / Default Stance / Version Notes” and keep inference separate from source facts.
- read files, write/modify files, read environment variables; may access external network resources; usually needs no extra API key.
- Validate with a small sample before expanding the task.
## Output requirements
- Return the deliverable, key evidence, validation method, and next action.
- Mark missing information as unknown; do not invent commands, platforms, or dependencies. The author source anchors workflow facts; repository files anchor sources and commands; Fluxly only adds fit, limitations, and quality judgment.
skill "golang-slog" {
input -> user goal + target files + boundaries + acceptance criteria
context -> When to Use / Default Stance / Version Notes
rules -> SKILL.md triggers / order / output contract
runtime -> no special runtime | read files, write/modify files, read environment variables | may access external network resources
guardrails -> usually needs no extra API key + small-sample validation + diff/log review
output -> copyable result + checklist + next iteration
} Golang slog
Use this skill when implementing structured logging in Go with the standard library log/slog package.
Use it alongside the structured-logging skill:
structured-loggingdefines the cross-language schema, field naming, safety, and observability rulesgolang-slogshows how to implement those rules idiomatically in Go withlog/slog
When to Use
- Adding structured logging to a Go service or CLI
- Replacing ad-hoc
log.Printfcalls with structured events - Standardising common log fields across Go services
- Building request-scoped, worker-scoped, or job-scoped loggers
- Implementing redaction, log schema normalisation, or dynamic log levels
Default Stance
Unless there is a clear reason not to, prefer these defaults:
- Use the standard library
log/slogfor Go 1.21+ - Prefer a
Formatconfig such asjsonortext, rather than a bareJSONboolean - Use
JSONHandlerin production andTextHandlerlocally - Construct the logger once during application startup
- Attach stable application metadata once with
Logger.With(...) - Keep the schema aligned with the
structured-loggingskill - Prefer typed attrs like
slog.String,slog.Int,slog.Int64,slog.Duration,slog.Bool - Prefer
InfoContext/ErrorContext/LogAttrswhen acontext.Contextis available - Use
WithGrouporslog.Group(...)for nested structured context - Use
ReplaceAttrandLogValuerfor schema normalisation and redaction - Pass
*slog.Loggeras a dependency instead of hiding it in global state when possible
Version Notes
log/slogis in the Go standard library starting with Go 1.21slog.MultiHandleris a newer addition; only use it if your project's Go version supports it- If your codebase targets older Go versions, use your existing logging stack instead of forcing
slog
Schema Alignment with structured-logging
The structured-logging skill recommends canonical fields like:
timestamplevelmessageserviceenvironmentapplication_versionapplication_commitrequest_idtrace_idspan_iduser_idoperationoutcomeduration_mserror_kinderror_codeerror_message
By default, slog emits built-in keys such as:
timelevelmsgsource
If you want Go logs to match the shared schema exactly, rename built-in keys with HandlerOptions.ReplaceAttr.
Recommended Logger Bootstrap
Centralise logger construction in main or a small bootstrap package.
package logging
import (
"fmt"
"io"
"log/slog"
)
type Format string
const (
FormatJSON Format = "json"
FormatText Format = "text"
)
type config struct {
environment string
service string
applicationVersion string
applicationCommit string
format Format
addSource bool
level slog.Leveler
replaceAttr func(groups []string, a slog.Attr) slog.Attr
}
type Option func(*config)
func WithEnvironment(v string) Option {
return func(c *config) { c.environment = v }
}
func WithService(v string) Option {
return func(c *config) { c.service = v }
}
func WithApplicationVersion(v string) Option {
return func(c *config) { c.applicationVersion = v }
}
func WithApplicationCommit(v string) Option {
return func(c *config) { c.applicationCommit = v }
}
func WithFormat(v Format) Option {
return func(c *config) { c.format = v }
}
func WithAddSource(v bool) Option {
return func(c *config) { c.addSource = v }
}
func WithLevel(v slog.Leveler) Option {
return func(c *config) { c.level = v }
}
func WithReplaceAttr(fn func(groups []string, a slog.Attr) slog.Attr) Option {
return func(c *config) { c.replaceAttr = fn }
}
func LevelVarFromString(v string) (*slog.LevelVar, error) {
var level slog.LevelVar
if err := level.UnmarshalText([]byte(v)); err != nil {
return nil, fmt.Errorf("parse log level %q: %w", v, err)
}
return &level, nil
}
func New(w io.Writer, opts ...Option) (*slog.Logger, error) {
cfg := config{
format: FormatJSON,
level: slog.LevelInfo,
replaceAttr: func(groups []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.TimeKey:
a.Key = "timestamp"
case slog.MessageKey:
a.Key = "message"
}
return a
},
}
for _, opt := range opts {
opt(&cfg)
}
handlerOptions := &slog.HandlerOptions{
AddSource: cfg.addSource,
Level: cfg.level,
ReplaceAttr: cfg.replaceAttr,
}
var handler slog.Handler
switch cfg.format {
case FormatJSON:
handler = slog.NewJSONHandler(w, handlerOptions)
case FormatText:
handler = slog.NewTextHandler(w, handlerOptions)
default:
return nil, fmt.Errorf("unsupported log format: %q", cfg.format)
}
logger := slog.New(handler).With(
slog.String("service", cfg.service),
slog.String("environment", cfg.environment),
slog.String("application_version", cfg.applicationVersion),
slog.String("application_commit", cfg.applicationCommit),
)
return logger, nil
}
Example use:
level, err := logging.LevelVarFromString("info")
if err != nil {
return err
}
logger, err := logging.New(os.Stdout,
logging.WithService("users-api"),
logging.WithEnvironment("production"),
logging.WithApplicationVersion("1.8.0"),
logging.WithApplicationCommit("a1b2c3d4"),
logging.WithFormat(logging.FormatJSON),
logging.WithLevel(level),
)
if err != nil {
return err
}
Guidance:
- Build one base logger at startup
- Prefer functional options when the bootstrap needs several optional settings
- Add
service,environment,application_version, andapplication_committhere - Prefer a format enum/string like
jsonortextover aJSON bool - Use
JSONHandlerfor production ingestion - Use
TextHandleronly when human readability is more important than strict machine parsing
Global vs Injected Logger
Prefer:
- Constructing the logger at startup
- Injecting
*slog.Loggerinto servers, services, workers, and repositories that need it
Avoid:
- Scattering calls to
slog.Default()throughout the codebase without a reason - Using package globals for everything
SetDefault is acceptable at the process boundary if you explicitly want top-level slog.Info(...) and legacy log.Print(...) calls to flow through the same handler, but keep that decision near startup.
Common Attribute Strategy
Stable Application Metadata
Attach these once to the base logger:
baseLogger := logger.With(
slog.String("service", "users-api"),
slog.String("environment", "production"),
slog.String("application_version", "1.8.0"),
slog.String("application_commit", "a1b2c3d4"),
)
Request- or Job-Scoped Metadata
Attach these at the request/job boundary:
requestLogger := baseLogger.With(
slog.String("request_id", requestID),
slog.String("trace_id", traceID),
slog.String("span_id", spanID),
slog.String("user_id", userID),
)
Prefer deriving child loggers with With(...) rather than repeating the same attrs on every line.
Context Usage
If a context.Context is available, prefer the ...Context forms:
logger.InfoContext(ctx, ...)logger.ErrorContext(ctx, ...)logger.LogAttrs(ctx, ...)
Why:
- custom handlers may extract trace/span metadata from the context
- it keeps logs aligned with request-scoped and tracing-aware systems
Rules:
- pass context when you already have one
- do not create fake contexts just to log
- do not treat context as a generic service locator
HTTP Request Pattern
Derive a request logger at the boundary and reuse it for the whole flow.
func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) {
start := time.Now()
logger := s.logger.With(
slog.String("request_id", requestIDFromRequest(r)),
slog.String("trace_id", traceIDFromContext(r.Context())),
slog.String("http_method", r.Method),
slog.String("http_route", "/users/{id}"),
)
user, err := s.userService.GetByID(r.Context(), chi.URLParam(r, "id"))
if err != nil {
logger.ErrorContext(r.Context(), "get user failed",
slog.String("operation", "get_user"),
slog.String("outcome", "failure"),
slog.String("error_kind", "user_lookup_failed"),
slog.String("error_message", "failed to fetch user"),
slog.Int64("duration_ms", time.Since(start).Milliseconds()),
)
return
}
logger.InfoContext(r.Context(), "http request completed",
slog.String("operation", "get_user"),
slog.String("outcome", "success"),
slog.Int("http_status_code", http.StatusOK),
slog.Int64("duration_ms", time.Since(start).Milliseconds()),
)
_ = user
}
Keep boundary logs summary-oriented. Avoid logging every internal function call in production.
Prefer Typed Attrs
Prefer:
logger.Info("user authenticated",
slog.String("user_id", userID),
slog.String("operation", "login"),
slog.Int64("duration_ms", duration.Milliseconds()),
)
Over large alternating key/value calls when they get dense or performance-sensitive.
Notes:
- alternating key/value syntax is valid and ergonomic for small calls
- typed attrs are clearer in larger calls
- typed attrs also avoid mistakes that produce
!BADKEY
Hot Paths and LogAttrs
If profiling shows logging overhead matters, prefer LogAttrs with typed attrs.
logger.LogAttrs(ctx, slog.LevelInfo, "cache refresh completed",
slog.String("operation", "refresh_cache"),
slog.String("outcome", "success"),
slog.Int64("duration_ms", duration.Milliseconds()),
)
Use this especially in high-frequency code paths.
Grouping Attributes
Use groups when nested context improves clarity or prevents key collisions.
logger.Info("request finished",
slog.Group("request",
slog.String("method", r.Method),
slog.String("route", "/users/{id}"),
),
slog.Group("auth",
slog.String("user_id", userID),
slog.String("actor_type", "user"),
),
)
Use WithGroup("http") or slog.Group("http", ...) when multiple subsystems would otherwise reuse keys like id, status, or method.
Error Logging
Log errors as structured events, not only as formatted strings.
Prefer:
logger.ErrorContext(ctx, "invoice payment failed",
slog.String("operation", "charge_invoice"),
slog.String("outcome", "failure"),
slog.String("error_kind", "payment_gateway_error"),
slog.String("error_code", "card_declined"),
slog.String("error_message", "payment provider declined charge"),
slog.String("user_id", userID),
slog.Int64("duration_ms", duration.Milliseconds()),
)
Guidance:
- prefer stable domain fields like
error_kindanderror_code - keep
error_messagesafe and sanitised - include the raw
errorvalue only when useful and safe - avoid logging the same failure at every layer
When you do need the raw error object:
logger.ErrorContext(ctx, "dependency call failed",
slog.Any("error", err),
slog.String("dependency", "billing-api"),
)
JSONHandler formats error-valued attrs as strings.
Redaction and Sensitive Data
Never log secrets directly.
Use LogValuer for type-level redaction:
type Token string
func (Token) LogValue() slog.Value {
return slog.StringValue("REDACTED")
}
Use ReplaceAttr for cross-cutting redaction or key normalisation:
opts := &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "authorization" || a.Key == "token" {
return slog.String(a.Key, "REDACTED")
}
return a
},
}
Prefer type-local redaction with LogValuer when the data type itself is sensitive. Use ReplaceAttr when you need a handler-wide policy.
Performance Guidance
- Use
Logger.With(...)for common attrs instead of repeating them - Prefer
LogAttrsin hot paths - Use typed attrs for common scalar values
- Avoid expensive string formatting before checking whether a level is enabled
- Pass values directly when possible instead of eagerly calling
.String() - Use
LogValuerto defer expensive computation for debug logs
Example:
if logger.Enabled(ctx, slog.LevelDebug) {
logger.DebugContext(ctx, "computed plan",
slog.Any("plan", buildDebugPlan(input)),
)
}
Avoid:
logger.DebugContext(ctx, "computed plan",
slog.String("plan", buildDebugPlan(input).String()),
)
if buildDebugPlan is expensive and debug logging may be disabled.
Dynamic Levels
Use slog.LevelVar when the level may change at runtime.
var level = new(slog.LevelVar)
level.Set(slog.LevelInfo)
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
}))
If your level comes from config, flags, or environment variables, a small helper is reasonable:
func LevelVarFromString(v string) (*slog.LevelVar, error) {
var level slog.LevelVar
if err := level.UnmarshalText([]byte(v)); err != nil {
return nil, fmt.Errorf("parse log level %q: %w", v, err)
}
return &level, nil
}
This is appropriate for CLIs, services with admin-configurable verbosity, or temporary incident debugging.
Bridging the Old log Package
If you still have legacy log.Print / log.Printf calls, you can centralise output with:
slog.SetDefault(logger)
That allows the default log package to emit through the configured slog handler.
Use this only as a deliberate application-wide choice, not as a substitute for dependency injection.
Testing Guidance
For tests around logging:
- write to a
bytes.Buffer - use
ReplaceAttrto remove or normalise time/source fields - assert on structured output, not on fragile formatting details
- prefer JSON output in tests when machine-readable assertions are easier
Example:
var buf bytes.Buffer
logger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
},
}))
Review Checklist
When reviewing Go slog usage, check for:
log/slogused consistently instead of mixed ad-hoc logging styles- base logger created centrally at startup
- stable common attrs attached with
With(...) - shared schema aligned with the
structured-loggingskill ReplaceAttrused when canonical key renaming is neededInfoContext/ErrorContextused when context already existsLogAttrsused in hot paths where it matters- structured error fields instead of only formatted strings
- redaction for secrets and sensitive values
- no repeated noisy logs for the same failure across layers
- JSON logs used for production ingestion
Anti-Patterns
Avoid these unless there is a strong reason:
- logging with
fmt.Sprintfinto one giant string - using
slog.Anyfor everything when typed attrs are clearer - eagerly computing expensive debug fields when debug is disabled
- forcing every function to call
slog.Default() - burying the logger in context as a universal service locator
- logging secrets, tokens, or raw auth headers
- leaving built-in keys as
time/msgwhen the rest of the platform expectstimestamp/message - emitting text logs in production when downstream systems expect JSON
- logging every internal function call instead of boundary summaries
Quick Template
level := new(slog.LevelVar)
level.Set(slog.LevelInfo)
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
switch a.Key {
case slog.TimeKey:
a.Key = "timestamp"
case slog.MessageKey:
a.Key = "message"
}
return a
},
})).With(
slog.String("service", "users-api"),
slog.String("environment", "production"),
slog.String("application_version", "1.0.0"),
slog.String("application_commit", "abcdef12"),
)
logger.Info("user authenticated",
slog.String("request_id", requestID),
slog.String("trace_id", traceID),
slog.String("user_id", userID),
slog.String("operation", "login"),
slog.String("outcome", "success"),
slog.Int64("duration_ms", duration.Milliseconds()),
)
Source: brpaz/agent-skills — distributed by TomeVault.
Decide Fit First
Design Intent
How To Use It
Boundaries And Review