# Go Logging with Slog
## Summary
The `log/slog` package in Go 1.21+ provides structured logging in the standard library. This resource presents an opinionated approach to using `slog` effectively, emphasizing direct usage of package-level functions (`slog.Info()`) over maintaining logger instances throughout code. The key benefits are cleaner code, consistent logging patterns, and easy integration with existing systems through the handler interface.
## Details
### Key Principles
1. **Use structured logging** - Log data as key-value pairs for better searchability and filtering
2. **Use the direct style** - Prefer `slog.Info()` calls over maintaining logger instances
3. **Configure once** - Set up your logging system once at application startup
4. **Consistent logging** - Ensure all parts of your system, including third-party libraries, log in a consistent format
### Using Slog Directly
The recommended approach is to use `slog` directly via the package-level functions. This creates cleaner code and follows the design philosophy of `slog` as a standardized logging interface.
```go
// Good: Direct usage
slog.Info("user authenticated", "userID", user.ID, "role", user.Role)
// Avoid: Instance-based approach in most cases
logger := slog.Default()
logger.Info("user authenticated", "userID", user.ID, "role", user.Role)
```
#### Why Direct Style?
1. Simplifies code by removing the need to pass logger instances around
2. Promotes consistent logging patterns across your codebase
3. Aligns with the package's design philosophy
4. Easy to change the underlying implementation globally
### Configuration
While direct usage is preferred, configuration still happens via a logger instance that is set as the default:
```go
func initLogging() {
// Create a handler (text, JSON, or custom)
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: &logLevel, // Using a LevelVar for dynamic level changes
AddSource: true, // Include source file and line information
})
// Set as default for both slog and standard log package
slog.SetDefault(slog.New(handler))
// Now all slog.Info() calls will use this configuration
slog.Info("logging initialized", "level", logLevel.Level())
}
```
#### Important: The Dual Role of SetDefault
The `slog.SetDefault()` function serves two crucial purposes:
1. It sets the default logger for all `slog` package-level functions
2. It updates the standard library's `log` package to use your logger
This second point is vital - it means any third-party packages using the standard `log` package will route through your structured logging system.
### Dynamic Log Levels
Use `slog.LevelVar` to dynamically adjust log levels:
```go
var logLevel = &slog.LevelVar{} // Defaults to LevelInfo
func init() {
// Set initial level based on environment
if os.Getenv("DEBUG") == "true" {
logLevel.Set(slog.LevelDebug)
}
}
// Later, you can change the level at runtime:
logLevel.Set(slog.LevelWarn) // Suppress less important logs
```
### Adding Context to Logs
While direct usage is preferred, there are valid cases for creating contextual loggers:
```go
// For components that require consistent context
dbLogger := slog.Default().With("component", "database")
dbLogger.Info("connected", "host", dbHost, "name", dbName)
// For request handlers
func HandleRequest(w http.ResponseWriter, r *http.Request) {
reqLogger := slog.Default().With(
"requestID", middleware.GetRequestID(r),
"path", r.URL.Path,
)
reqLogger.Info("request started")
// Process request...
reqLogger.Info("request completed", "status", 200)
}
```
### Context Propagation
When available, pass context to logging functions:
```go
// Prefer context variants when context is available
slog.InfoContext(ctx, "operation completed", "duration", duration)
```
This allows handlers to extract context values like trace IDs.
### Performance Considerations
For high-performance logging, use these techniques:
```go
// Most efficient way to log
slog.LogAttrs(ctx, slog.LevelInfo, "message",
slog.String("user", username),
slog.Int("count", count),
slog.Bool("admin", isAdmin))
// Pre-computed attributes for frequently used loggers
authLogger := slog.Default().With(
"component", "auth",
"version", appVersion,
)
```
### Custom Types and Logging
Implement the `LogValuer` interface for cleaner logs:
```go
type User struct {
ID int
Name string
Email string
Password string
}
// LogValue implements the slog.LogValuer interface
func (u User) LogValue() slog.Value {
return slog.GroupValue(
slog.Int("id", u.ID),
slog.String("name", u.Name),
slog.String("email", u.Email),
// Note: Password intentionally omitted for security
)
}
// Now you can log the user directly
slog.Info("user profile updated", "user", user)
```
### Integration with Existing Systems
For integrating with existing logging systems, create a handler that bridges to your existing system:
```go
// Example: Handler that routes to an existing logger
type ExistingLoggerHandler struct {
existingLogger *existing.Logger
}
func (h *ExistingLoggerHandler) Handle(ctx context.Context, record slog.Record) error {
// Extract and format attributes
attrs := make(map[string]interface{})
record.Attrs(func(attr slog.Attr) bool {
attrs[attr.Key] = attr.Value.Any()
return true
})
// Log using existing system
switch record.Level {
case slog.LevelDebug:
h.existingLogger.Debug(record.Message, attrs)
case slog.LevelInfo:
h.existingLogger.Info(record.Message, attrs)
// ...other levels
}
return nil
}
// Implement other required methods...
```
## đź”— Related Resources
- [Official `slog` Documentation](https://pkg.go.dev/log/slog)
- [Go Blog: Structured Logging with slog](https://go.dev/blog/slog)
- [Resources for slog](https://go.dev/wiki/Resources-for-slog)
- [Handler Writing Guide](https://github.com/golang/example/blob/master/slog-handler-guide/README.md)
- [Functional Options Pattern in Go](/Users/jp/notes/3.Resources/Functional%20Options%20Pattern%20in%20Go.md)
- [Golang Testing Mocking Strategies](/Users/jp/notes/3.Resources/Golang%20Testing%20Mocking%20Strategies.md)