Lightweight logger built on Go log/slog, with console/file output, structured fields, custom templates, middleware, context attributes, and file rotation.
Requires Go 1.25+
- Multi-output: Console & file with independent configuration
- Flexible formats: Text, JSON, and custom template support
- Smart coloring: ANSI colors for console (auto-disabled for files)
- Auto-rotation: Size-based rotation with configurable retention and gzip compression
- Structured logging: Full
slogAPI with groups and attributes - Middleware: Composable handler middleware chain
- Context attributes: Inject
slog.Attrviacontext.Context - Dynamic level: Change log level at runtime via
SetLevel() - Attribute transformation: Custom processing via
WithReplaceAttr - Drop-in replacement: Embeds
*slog.Logger+ easySetDefault() - Efficient runtime: Buffered file writes and optimized custom formatter path
go get github.com/simp-lee/loggerpackage main
import "github.com/simp-lee/logger"
func main() {
log, err := logger.New()
if err != nil {
panic(err)
}
defer log.Close()
log.Info("Hello, world!")
log.Info("User login", "userId", 123, "username", "johndoe")
}All options are functional options passed to logger.New(...). Unspecified fields use defaults from DefaultConfig().
- Options are applied in order; later options override earlier ones.
WithFilePath(path)automatically enables file output.- If
WithFile(true)is set without a path,New()returns an error. - If both console and file are disabled, logger creation still succeeds and falls back to a default
slog.TextHandleronos.Stderr.
| Option | Description | Default |
|---|---|---|
WithLevel(slog.Level) |
Minimum logging level | slog.LevelInfo |
WithLevelVar(*slog.LevelVar) |
Use a shared LevelVar for dynamic level control across loggers |
auto-created internally |
WithAddSource(bool) |
Include source file information (filename:function:line) |
false |
WithTimeFormat(string) |
Go time format string for timestamps | "2006/01/02 15:04:05" |
WithTimeZone(*time.Location) |
Time zone for timestamps | time.Local |
WithReplaceAttr(func) |
Custom attribute transformation function | nil |
WithMiddleware(...Middleware) |
Append handler middlewares (applied in order) | nil |
| Option | Description | Default |
|---|---|---|
WithConsole(bool) |
Enable/disable console logging | true |
WithConsoleWriter(io.Writer) |
Console output destination | os.Stderr |
WithConsoleColor(bool) |
Enable ANSI colored output | true |
WithConsoleFormat(OutputFormat) |
Console log format (FormatText, FormatJSON, FormatCustom) |
FormatCustom |
WithConsoleFormatter(string) |
Custom format template for console (auto-sets format to FormatCustom) |
"{time} {level} {message} {file} {attrs}" |
| Option | Description | Default |
|---|---|---|
WithFile(bool) |
Enable/disable file logging (requires non-empty path when enabled) | false |
WithFilePath(string) |
Path to the log file (auto-enables file logging) | "" |
WithFileFormat(OutputFormat) |
File log format (FormatText, FormatJSON, FormatCustom) |
FormatCustom |
WithFileFormatter(string) |
Custom format template for file (auto-sets format to FormatCustom) |
"{time} {level} {message} {file} {attrs}" |
WithMaxSizeMB(int) |
Max file size in MB before rotation (0 = disable rotation, negative = default) |
10 |
WithRetentionDays(int) |
Days to retain rotated files (<=0 resets to default) |
7 |
WithCompressRotated(bool) |
Gzip compress rotated log files | false |
WithMaxBackups(int) |
Max number of rotated backups to keep (0 = unlimited) |
0 |
Set both console and file configurations at once:
| Option | Description |
|---|---|
WithFormat(OutputFormat) |
Set log format for both console and file |
WithFormatter(string) |
Set custom template for both console and file (auto-sets format to FormatCustom) |
Three built-in formats:
| Format | Constant | Description |
|---|---|---|
| Text | FormatText |
Standard slog.TextHandler output |
| JSON | FormatJSON |
Standard slog.JSONHandler output |
| Custom | FormatCustom |
Template-based format with placeholders |
| Placeholder | Content |
|---|---|
{time} |
Timestamp (formatted by WithTimeFormat + WithTimeZone) |
{level} |
Log level (DEBUG, INFO, WARN, ERROR) |
{message} |
Log message |
{file} |
Source location filename:function:line (requires WithAddSource(true)) |
{attrs} |
User attributes (key=value ...) |
Empty placeholders (e.g. {file} without AddSource) are trimmed with surrounding whitespace automatically.
log, err := logger.New(
logger.WithConsoleFormatter("{time} | {level} | {message} | {attrs}"),
)
if err != nil {
panic(err)
}
defer log.Close()Console coloring (ANSI) is toggled with WithConsoleColor(true/false). File output never includes color codes.
Level color mapping: DEBUG = Bright Cyan, INFO = Green, WARN = Yellow, ERROR = Red (message text, error attribute key and value all emphasized).
Formats are independent per output. Common pattern — human-readable console + structured file:
log, err := logger.New(
logger.WithConsoleFormat(logger.FormatCustom),
logger.WithConsoleFormatter("{time} {level} {message}"),
logger.WithConsoleColor(true),
logger.WithFilePath("./logs/app.log"),
logger.WithFileFormat(logger.FormatJSON),
)
if err != nil {
panic(err)
}
defer log.Close()- Trigger: Current file size exceeds
WithMaxSizeMB(N)MB. Set0to disable rotation. - Naming:
basename.YYYYMMDD.HHMMSS.mmm.ext(adds.countersuffix on collision). - Compression: Enable with
WithCompressRotated(true)— rotated files are gzipped asynchronously (.gzsuffix). - Retention: Files older than
WithRetentionDays(D)are purged daily. - Backup limit:
WithMaxBackups(N)caps the number of rotated files kept (oldest removed first).0= unlimited. - Lazy open: The log file is not opened until the first write, avoiding leaked file descriptors for unused loggers.
- Buffered I/O: Writes go through a 64 KB buffer; call
Sync()to flush explicitly.
log, err := logger.New(
logger.WithFilePath("./logs/app.log"),
logger.WithMaxSizeMB(50),
logger.WithRetentionDays(30),
logger.WithCompressRotated(true),
logger.WithMaxBackups(10),
)
if err != nil {
panic(err)
}
defer log.Close()Middleware is a func(slog.Handler) slog.Handler that wraps the final handler. Middlewares are applied in declaration order via WithMiddleware.
// Example: add a request ID to every log record
func RequestIDMiddleware(requestID string) logger.Middleware {
return func(next slog.Handler) slog.Handler {
return next.WithAttrs([]slog.Attr{slog.String("request_id", requestID)})
}
}
log, err := logger.New(
logger.WithMiddleware(RequestIDMiddleware("abc-123")),
)ContextMiddleware() extracts slog.Attr values stored in a context.Context and appends them to each log record.
log, err := logger.New(
logger.WithMiddleware(logger.ContextMiddleware()),
)
if err != nil {
panic(err)
}
defer log.Close()
// Store attributes in context
ctx := logger.WithContextAttrs(context.Background(),
slog.String("request_id", "req-001"),
slog.String("user_id", "u-42"),
)
// Attributes are automatically included in log output
log.InfoContext(ctx, "processing request")Context helpers:
| Function | Description |
|---|---|
WithContextAttrs(ctx, ...slog.Attr) |
Returns a new context with the given attributes appended |
FromContext(ctx) |
Extracts stored []slog.Attr from context (returns a copy) |
Intercept and edit/remove attributes (built-in keys: time, level, msg, source, plus user attrs). Return an empty slog.Attr{} to drop an attribute.
log, err := logger.New(
logger.WithReplaceAttr(func(groups []string, a slog.Attr) slog.Attr {
if a.Key == "password" {
return slog.String("password", "***")
}
return a
}),
)Logger embeds *slog.Logger, so all standard slog methods (e.g. Info, Warn, Error, Debug, With, WithGroup, InfoContext, etc.) are available directly.
Additional methods on *Logger:
| Method | Description |
|---|---|
Close() error |
Release all resources (files, goroutines, timers). Always defer this. |
SetDefault() |
Set this logger as the default for slog.Info(), slog.Error(), etc. |
SetLevel(slog.Level) |
Dynamically change the minimum log level at runtime. No-op for Default() loggers. |
Rotate() error |
Trigger log file rotation immediately. No-op if file logging is not enabled. |
Sync() error |
Flush buffered data to the underlying file. No-op if file logging is not enabled. |
| Function | Description |
|---|---|
New(...Option) (*Logger, error) |
Create a configured logger with resource management. Recommended. |
Default() *Logger |
Wrap slog.Default(). Does not support SetLevel(). No Close() needed. |
log, err := logger.New(logger.WithAddSource(true))
if err != nil {
panic(err)
}
defer log.Close()
log.SetDefault()
slog.Info("routed through custom logger", "module", "auth")log, err := logger.New(logger.WithLevel(slog.LevelInfo))
if err != nil {
panic(err)
}
defer log.Close()
log.Debug("hidden") // not printed (level is Info)
log.SetLevel(slog.LevelDebug)
log.Debug("now visible") // printedpackage main
import (
"context"
"log/slog"
"github.com/simp-lee/logger"
)
func main() {
log, err := logger.New(
logger.WithLevel(slog.LevelDebug),
logger.WithAddSource(true),
logger.WithConsoleFormatter("{time} [{level}] {message} {attrs}"),
logger.WithFilePath("./logs/app.log"),
logger.WithFileFormat(logger.FormatJSON),
logger.WithMaxSizeMB(50),
logger.WithRetentionDays(30),
logger.WithCompressRotated(true),
logger.WithMiddleware(logger.ContextMiddleware()),
)
if err != nil {
panic(err)
}
defer log.Close()
log.SetDefault()
log.Info("startup", "version", "1.0.0")
ctx := logger.WithContextAttrs(context.Background(),
slog.String("request_id", "req-001"),
)
log.InfoContext(ctx, "handling request", "path", "/api/users")
userLogger := log.WithGroup("user")
userLogger.Info("login", "id", 42)
}# Constructors
logger.New(opts ...Option) (*Logger, error)
logger.Default() *Logger
# Logger methods (in addition to embedded *slog.Logger)
(*Logger) Close() error
(*Logger) SetDefault()
(*Logger) SetLevel(slog.Level)
(*Logger) Rotate() error
(*Logger) Sync() error
# Global options
WithLevel(slog.Level) WithLevelVar(*slog.LevelVar)
WithAddSource(bool) WithTimeFormat(string)
WithTimeZone(*time.Location) WithReplaceAttr(func([]string, slog.Attr) slog.Attr)
WithMiddleware(...Middleware)
# Console options
WithConsole(bool) WithConsoleWriter(io.Writer)
WithConsoleColor(bool) WithConsoleFormat(OutputFormat)
WithConsoleFormatter(string)
# File options
WithFile(bool) WithFilePath(string)
WithFileFormat(OutputFormat) WithFileFormatter(string)
WithMaxSizeMB(int) WithRetentionDays(int)
WithCompressRotated(bool) WithMaxBackups(int)
# Shorthand options
WithFormat(OutputFormat) WithFormatter(string)
# Output formats
FormatText FormatJSON FormatCustom
# Context helpers
WithContextAttrs(ctx, ...slog.Attr) context.Context
FromContext(ctx) []slog.Attr
# Built-in middleware
ContextMiddleware() Middleware
# Types
type Middleware func(slog.Handler) slog.Handler
type OutputFormat string // "text" | "json" | "custom"
MIT License