Go’s slog
package, introduced in Go 1.21, provides a structured logging interface that’s both powerful and flexible. One of its key features is the log level system, which helps developers control what messages appear in their logs. Let’s dive deep into how these levels work.
The Logging Level Hierarchy
The slog
package implements a hierarchical level system where each level represents a different severity of log message. The hierarchy, from lowest to highest severity, is:
- Debug
- Info
- Warn
- Error
How Level Filtering Works
The logging level you set acts as a minimum threshold. Any log message with a severity level equal to or higher than this threshold will be displayed, while messages with lower severity levels will be filtered out.
Example Setup
Here’s a basic example showing two different logger configurations:
package main
import (
"log/slog"
"os"
)
func main() {
// Debug level logger
debugLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
// Info level logger
infoLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
}
Debug Level Configuration
When you set the logger level to Debug:
debugLogger.Debug("Debug message") // ✅ Will be displayed
debugLogger.Info("Info message") // ✅ Will be displayed
debugLogger.Warn("Warning message") // ✅ Will be displayed
debugLogger.Error("Error message") // ✅ Will be displayed
Info Level Configuration
When you set the logger level to Info:
infoLogger.Debug("Debug message") // ❌ Won't be displayed
infoLogger.Info("Info message") // ✅ Will be displayed
infoLogger.Warn("Warning message") // ✅ Will be displayed
infoLogger.Error("Error message") // ✅ Will be displayed
Common Pitfalls and Solutions
1. Default Level
If you don’t specify a level in the HandlerOptions
, the logger defaults to Info level. This means Debug messages won’t be visible unless explicitly configured:
// This logger will use Info level by default
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
2. Environment-Based Configuration
It’s common to want different log levels in different environments. Here’s a pattern for setting the level based on an environment variable:
func getLogLevel() slog.Level {
levelStr := os.Getenv("LOG_LEVEL")
switch levelStr {
case "DEBUG":
return slog.LevelDebug
case "WARN":
return slog.LevelWarn
case "ERROR":
return slog.LevelError
default:
return slog.LevelInfo
}
}
func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: getLogLevel(),
}))
// Use logger...
}
Best Practices
Development vs. Production: Use Debug level during development for maximum visibility, but consider using Info level in production to reduce noise and improve performance.
Appropriate Level Usage:
- Debug: Detailed information for debugging
- Info: General operational messages
- Warn: Warning messages for potentially harmful situations
- Error: Error messages for serious problems
Structured Logging: Take advantage of slog’s structured logging capabilities:
logger.Info("user login",
"username", user.Name,
"loginTime", time.Now(),
"attemptNumber", attempts)
Conclusion
Understanding how slog’s log levels work is crucial for effective logging in Go applications. By setting appropriate log levels and following best practices, you can maintain clean and informative logs that help with both debugging and monitoring your application.
Remember that log levels are just one part of a good logging strategy. Combined with structured logging and proper log management, they help create a robust observability solution for your Go applications.