silog is a terminal-friendly logging handler for Go's standard log/slog package.
- Configurable colorful output
- Custom log levels
- Multi-line log attributes
Built-in levels are demonstrated with their default color-coded styling.
logger.Debug("Starting background task")
logger.Info("Server listening on :8080")
logger.Warn("Connection pool nearing capacity")
logger.Error("Failed to process request")Multi-line error messages are formatted with each line receiving the timestamp and level prefix.
logger.Error("Database connection failed:\nConnection refused\n at db.Connect()\n at main.startup()")Multi-line attribute values are formatted with pipe-prefixed indentation for readability.
logger.Info("Request completed",
"sql", "SELECT *\nFROM users\nWHERE active = true",
"duration", "45ms",
)Nested grouped attributes are created using WithGroup and slog.Group, rendered with dot-notation for hierarchical attribute organization.
logger := slog.New(handler).WithGroup("request").WithGroup("headers")
logger.Info("Incoming request",
slog.Group("body",
"method", "POST",
"path", "/api/users",
),
)The "error" attribute is automatically highlighted in red when using the default style.
logger.Info("Operation failed",
"error", "connection timeout",
"retry_count", 3,
"timeout", "30s",
)Handler-level prefixes are used to distinguish log messages posted by different subsystems or components.
dbHandler := baseHandler.WithPrefix("database")
cacheHandler := baseHandler.WithPrefix("cache")
dbLogger := slog.New(dbHandler)
cacheLogger := slog.New(cacheHandler)
dbLogger.Info("Connection pool initialized")
cacheLogger.Warn("Cache miss rate high")Prefixes are preserved across multi-line messages, appearing on each line of output.
log := slog.New(h.WithPrefix("worker"))
log.Info("Task completed:\n- Processed 1000 items\n- Generated 50 reports\n- Sent 25 notifications")A complex real-world example demonstrating combining multi-line error attributes, structured key-value pairs, and error highlighting.
log.Error("API request failed",
"method", "POST",
"path", "/api/orders",
"error", "validation failed:\n - invalid email\n - missing required field: address",
"user_id", "12345",
"duration", "125ms",
)The With method is used to pre-attach attributes for shared context across multiple log statements.
requestLog := log.With("request_id", "abc-123", "user", "[email protected]", "session", "xyz-789")
requestLog.Info("Request started")
requestLog.Info("Processing payment")
requestLog.Info("Request completed", "duration", "245ms")A custom log level can be defined with its own label and message style.
style.LevelLabels[LevelTrace] = renderer.NewStyle().SetString("TRC")
style.Messages[LevelTrace] = style.Messages[slog.LevelDebug]
logger.Log(context.Background(), LevelTrace, "Entering function")WithLevelOffset dynamically downgrades log levels for testing or temporarily reducing log verbosity.
logger := slog.New(baseHandler.WithLevelOffset(-4))
logger.Error("This appears as WARNING")
logger.Warn("This appears as INFO")
logger.Info("This appears as DEBUG")Multi-line attribute values within deeply nested groups, show how formatting is preserved through multiple levels.
logger := baseLogger.WithGroup("service").WithGroup("database")
logger.Info("Query executed",
slog.Group("details",
"query", "SELECT id, name\nFROM users\nORDER BY created_at",
"rows", 42))ReplaceAttr can suppress the timestamp from log output.
ReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {
// Remove the time attribute from log output.
if len(groups) == 0 && attr.Key == slog.TimeKey {
return slog.Attr{}
}
return attr
},This library was originally developed as part of git-spice. It's been extracted into a separate repository to allow use from other projects.
This software is made available under the BSD-3 license. See LICENSE for the full license text.
