# 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)