From a Log Key Mistake to Safer Go slog: Lessons and Practical Tricks
After a production incident caused by a wrong log field name, the author explores Go's standard slog library, reveals its classic ...any trap, and presents four concrete techniques—dependency‑injected logger, type‑safe LogAttrs, centralized field helpers, and sloglint enforcement—to build a reliable, maintainable logging system.
"Unreviewed logs are not worth output" – a colleague warned the author when a recent deployment produced logs with the field order_id mistakenly replaced by !BADKEY. The mistake traced back to a missing argument in a call to slog.Info, illustrating how slog’s ...any variadic API lets key‑value mismatches slip past the compiler and surface only at runtime.
Why I briefly left zap for slog
Before Go 1.21 the author was a zap enthusiast for its performance and ecosystem. The introduction of the standard slog package sparked a reconsideration because it removes an external dependency and avoids worries about future maintenance.
Four practical tricks to make slog type‑safe and maintainable
1. Treat the logger as an injected object, not a global
Many tutorials call slog.Info(), which uses a mutable global logger. This leads to test interference and configuration clashes. Instead, define a *slog.Logger field in structs and pass it via constructors, e.g.:
type OrderService struct {
logger *slog.Logger // explicit dependency
}
func NewOrderService(logger *slog.Logger) *OrderService {
return &OrderService{logger: logger}
}Initialize the logger once in main and inject a test logger during unit tests. This pattern eliminates most "why my test logs are missing" issues.
2. Use LogAttrs for compile‑time type safety
The convenience methods Info, Warn still accept ...any, relying on developer discipline. LogAttrs requires explicit attribute constructors such as slog.String or slog.Int64, causing a compile error if a wrong type is supplied.
logger.LogAttrs(ctx, slog.LevelInfo, "order placed",
slog.String("order_id", id),
slog.Int64("amount_cents", amount),
)The author argues that a compile‑time failure is far cheaper than spending hours debugging malformed JSON logs in production.
3. Centralize field definitions in a helper package
Repeatedly writing slog.String("order_id", id) is error‑prone when field names change. The solution is a small internal package that defines helpers returning ready‑made slog.Attr values:
package log
import "log/slog"
func OrderID(id string) slog.Attr {
return slog.String("order_id", id) // single source of truth
}
func AmountCents(c int64) slog.Attr {
return slog.Int64("amount_cents", c)
}
func Err(e error) slog.Attr {
if e == nil {
return slog.Attr{}
}
return slog.String("err", e.Error())
}Calls become concise and renaming a field only requires editing attrs.go, which propagates site‑wide.
4. Let sloglint enforce the conventions
To prevent accidental re‑introduction of the ...any style or global functions, the author configures the sloglint plugin for golangci‑lint:
linters-settings:
sloglint:
attr-only: true # forbid kv style, require LogAttrs
no-global: "all" # forbid slog.Info, etc.
context: "all" # require a context argument
static-msg: true # log message must be a literal string
key-naming-case: snake # enforce snake_case keysWith this configuration, a CI run rejects code like Info("msg", "key", val) before it reaches review.
Benefits and limits
Rename safety : changing a key in attrs.go updates every log instantly.
Type protection : passing an int where int64 is required fails to compile.
Privacy control : helper functions can mask or redact sensitive fields centrally.
Performance : LogAttrs avoids the reflection and type‑assertion overhead of the generic any path, which adds up in high‑frequency logging.
However, type safety does not guarantee semantic correctness; a function like AmountCents(int64) still cannot prevent passing cents when the business expects dollars. The author’s principle is to let the type system block syntax errors and devote human effort to semantic validation.
Conclusion
Although slog may not be the fastest logger, its standard‑library status and the ability to combine LogAttrs, dependency injection, centralized helpers, and lint enforcement provide a robust, type‑safe logging foundation for production services.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Golang Shines
We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
