Go cookbook · Beginner
Log structured JSON with slog and redact secrets
Logs are unstructured strings, and tokens or passwords end up in them.
Approach
Create a JSON slog handler with a level from configuration, attach common attributes with With, and
implement slog.LogValuer on types that contain secrets so they're always redacted.
Solution
package main
import (
"log/slog"
"os"
)
type APIKey string
func (APIKey) LogValue() slog.Value { return slog.StringValue("[redacted]") }
type User struct {
ID int
Email string
Key APIKey
}
func (u User) LogValue() slog.Value {
return slog.GroupValue(slog.Int("id", u.ID), slog.String("email_domain", domain(u.Email)))
}
func domain(email string) string {
for i := range email {
if email[i] == '@' {
return email[i+1:]
}
}
return ""
}
func main() {
level := new(slog.LevelVar)
level.Set(slog.LevelInfo)
if os.Getenv("LOG_LEVEL") == "debug" {
level.Set(slog.LevelDebug)
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
logger = logger.With("service", "billing", "version", os.Getenv("APP_VERSION"))
slog.SetDefault(logger)
user := User{ID: 7, Email: "[email protected]", Key: "sk_live_123"}
slog.Info("charge created", "user", user, "amount_cents", 2400, "key", user.Key)
slog.Debug("only logged at debug level")
}
Pitfalls
- Building messages with
fmt.Sprintfinstead of attributes, which makes logs hard to query. - Logging whole request bodies or structs with secrets.
- Using different keys for the same thing (
userId,user_id); agree on names.
Variations
- Add trace and span IDs from OpenTelemetry with a custom handler.
- Use
slog.LevelVarto change the level at runtime (for example from an admin endpoint).
Further reading: log/slog
Reference: https://pkg.go.dev/log/slog
More Go recipes
- Benchmark code with testing.B.Loop
- Build a small, secure container image for a Go service
- Decode and validate JSON request bodies safely
- Embed static files and templates in the binary
- Load and validate configuration from environment variables
- Process jobs concurrently with a bounded worker pool
- Profile a running service with pprof
- Rate limit requests per client