Fast and feature-rich structured logging library for Go
- Features
- Production Tested
- Installation
- Quick Start
- Log Levels
- Sub-loggers and Context
- Multiple Writers and Filtering
- Terminal Detection
- Ready-to-Use Logger (
logsubpackage) - Rotating Log Files
- Standard Library Integration (slog)
- HTTP Middleware
- Advanced Features
- Performance
- Comparison with Other Logging Libraries
- API Reference
- License
- High Performance: Zero-allocation logging for JSON, text, and complex fields (error, time.Time)
- Structured Logging: Type-safe field methods for all Go primitives including native time.Time and UUID
- Multiple Output Formats: JSON and human-readable text output
- Terminal Auto-Detection: Automatically switches between colored text (TTY) and JSON (non-TTY)
- Configurable Log Levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL with flexible filtering
- Context Support: Log attributes can be stored in and retrieved from context automatically
- Duplicate Key Prevention: Prevents accidental duplicate keys in log output
- Colorized Output: Beautiful colored console output with customizable colorizers
- Multi-Writer Architecture: Log to multiple destinations with different formats and filters
- Rotating Log Files: Automatic file rotation based on size thresholds
- slog Integration: Use as a backend for Go's standard log/slog package
- HTTP Middleware: Built-in HTTP request/response logging with request ID propagation
- UUID Support: Native UUID logging with zero allocations
- Call Stack Tracing: Capture and log call stacks for debugging
- Memory Safety: Nil-safe logger implementation prevents panics
- Sub-loggers: Create child loggers with inherited attributes
golog has been used in production at domonda since 2023, powering mission-critical business applications with reliable, high-performance logging.
go get github.com/domonda/gologpackage main
import (
"errors"
"os"
"github.com/domonda/golog"
)
func main() {
// Create a basic text logger
config := golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.NewTextWriterConfig(os.Stdout, nil, nil),
)
log := golog.NewLogger(config)
// Simple logging
log.Info("Hello, World!").Log()
log.Error("Something went wrong").Err(errors.New("example error")).Log()
}// Create a JSON logger
config := golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.NewJSONWriterConfig(os.Stdout, nil),
)
log := golog.NewLogger(config)
start := time.Now()
// ... perform login ...
log.Info("User login").
Str("username", "john_doe").
Str("ip", "192.168.1.1").
Duration("login_time", time.Since(start)).
Log()Output:
{"timestamp":"2024-01-15T10:30:45Z","level":"INFO","message":"User login","username":"john_doe","ip":"192.168.1.1","login_time":"150ms"}requestID := golog.UUIDv4() // [16]byte UUID
jsonBytes := []byte(`{"key":"value"}`) // Raw JSON
log.Info("Processing request").
Str("method", "POST").
Str("path", "/api/users").
Int("user_id", 12345).
Bool("authenticated", true).
Float("response_time", 0.145).
UUID("request_id", requestID).
Time("started_at", time.Now()).
Strs("tags", []string{"api", "user", "create"}).
JSON("metadata", jsonBytes).
Log()golog supports six standard log levels:
- TRACE (-20): Most verbose, for tracing execution flow
- DEBUG (-10): Debug information for development
- INFO (0): General information messages
- WARN (10): Warning messages for potentially harmful situations
- ERROR (20): Error conditions that don't require immediate attention
- FATAL (30): Critical errors that may cause application termination
log.Trace("Entering function").Str("function", "processData").Log()
log.Debug("Variable state").Int("counter", 42).Log()
log.Info("Operation completed").Log()
log.Warn("Deprecated API used").Str("api", "/old/endpoint").Log()
log.Error("Failed to connect").Err(err).Log()
log.Fatal("Critical system failure").Log()requestID := golog.UUIDv4()
// Create a sub-logger with common attributes
subLog := log.With().
Str("service", "user-management").
UUID("request_id", requestID).
SubLogger()
// All logs from subLog will include the above attributes
subLog.Info("User created").Str("username", "john").Log()
subLog.Error("User validation failed").Err(errors.New("invalid email")).Log()// Add attributes to context
ctx = golog.ContextWithAttribs(ctx,
golog.NewString("correlation_id", "abc-123"),
golog.NewString("user_id", "user-456"),
)
// Create logger from context
ctxLogger := log.WithCtx(ctx)
ctxLogger.Info("Operation started").Log() // Includes context attributes
// Or use the logger's InfoCtx method
logger.InfoCtx(ctx, "Operation started").Log() // Includes context attributeslogFile, _ := os.Create("app.log")
defer logFile.Close()
// Log to multiple outputs with different formats
config := golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.NewTextWriterConfig(os.Stdout, nil, golog.NewStyledColorizer()),
// FilterOutBelow filters out less severe levels (lower numeric values)
// Warn=10, so this filters out Trace=-20, Debug=-10, Info=0
// Only Warn=10, Error=20, Fatal=30 will be logged to the file
golog.NewJSONWriterConfig(logFile, nil, golog.DefaultLevels.Warn.FilterOutBelow()),
)
log := golog.NewLogger(config)
// This will appear in colored text on stdout and as JSON in the file (if WARN+)
log.Error("Database connection failed").Err(errors.New("connection refused")).Log()golog can automatically switch between human-readable text and machine-readable JSON output based on whether the process is attached to a terminal (TTY):
config := golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.DecideWriterConfigForTerminal(
golog.NewTextWriterConfig(os.Stdout, nil, golog.NewStyledColorizer()), // Used when running in terminal
golog.NewJSONWriterConfig(os.Stdout, nil), // Used when output is piped/redirected
),
)
log := golog.NewLogger(config)- Terminal (TTY): Outputs colored, human-readable text format
- Non-terminal: Outputs machine-readable JSON format (ideal for log aggregation systems)
You can also check the terminal status directly:
if golog.IsTerminal() {
// Running in a terminal
}This feature is useful for:
- Development: Human-readable logs in the terminal
- Production: Machine-parseable JSON logs for log aggregation systems
- CI/CD: Automatic format switching based on the environment
- Containerized Applications: Proper format when logs are piped to files or log collectors
For quick setup, the log subpackage provides a pre-configured logger with sensible defaults:
import (
"errors"
"github.com/domonda/golog/log"
)
func main() {
log.Info("Application started").Log()
log.Error("Something went wrong").Err(errors.New("example error")).Log()
log.Debug("Debug information").Str("key", "value").Log()
}The log package is configured with:
- Log Levels: Uses
golog.DefaultLevels(TRACE, DEBUG, INFO, WARN, ERROR, FATAL) - Level Filter: Filters out levels below
LOG_LEVELenvironment variable (defaults to DEBUG if not set) - Output: Writes to stdout with automatic format selection:
- Terminal: Colorized text format for human readability
- Non-terminal: JSON format for log aggregation systems
The log.Config variable can be modified at runtime:
import "github.com/domonda/golog/log"
func init() {
// Change the minimum log level
// Info=0, so FilterOutBelow filters out Trace=-20 and Debug=-10
// Only Info=0, Warn=10, Error=20, Fatal=30 will be logged
log.Config = golog.NewConfig(
log.Levels,
log.Levels.Info.FilterOutBelow(),
golog.DecideWriterConfigForTerminal(
golog.NewTextWriterConfig(os.Stdout, &log.Format, &log.Colorizer),
golog.NewJSONWriterConfig(os.Stdout, &log.Format),
),
)
}The logger uses a DerivedConfig that references log.Config, so changes to log.Config take effect immediately without recreating the logger.
The log package exposes convenience functions for all log levels:
log.Trace("message").Log()
log.Debug("message").Log()
log.Info("message").Log()
log.Warn("message").Log()
log.Error("message").Log()
log.Fatal("message").Log()
// With context - attributes added to context are automatically included
ctx := golog.ContextWithAttribs(context.Background(),
golog.NewString("request_id", "req-123"),
golog.NewString("user_id", "user-456"),
)
log.InfoCtx(ctx, "processing request").Log() // Includes request_id and user_id
// Formatted messages
log.Infof("User %s logged in", "john_doe").Log()
// Create sub-loggers
subLog := log.With().Str("component", "auth").SubLogger()Automatic file rotation based on size thresholds using the logfile subpackage:
import (
"github.com/domonda/golog"
"github.com/domonda/golog/logfile"
)
// Create a rotating writer that rotates at 10MB
writer, err := logfile.NewRotatingWriter(
"/var/log/myapp.log", // File path
logfile.RotatingWriterDefaultTimeFormat, // Time format for rotated files
0644, // File permissions
10*1024*1024, // Rotate at 10MB
)
if err != nil {
log.Fatal(err)
}
defer writer.Close()
// Use with golog
config := golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.NewJSONWriterConfig(writer, nil),
)
log := golog.NewLogger(config)
log.Info("Application started").Log()When the log file reaches 10MB:
- The current file is renamed with a timestamp (e.g.,
myapp.log.2024-01-15_10:30:45) - A new file is created at the original path
- Logging continues seamlessly to the new file
// Console output with colors + rotating JSON file
fileWriter, _ := logfile.NewRotatingWriter("/var/log/app.log", "", 0644, 50*1024*1024)
defer fileWriter.Close()
config := golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.NewTextWriterConfig(os.Stdout, nil, golog.NewStyledColorizer()),
golog.NewJSONWriterConfig(fileWriter, nil),
)
log := golog.NewLogger(config)See the logfile package documentation for more details.
Use golog as a backend for Go's standard log/slog package via the goslog adapter:
import (
"log/slog"
"github.com/domonda/golog"
"github.com/domonda/golog/goslog"
)
// Create golog logger
gologLogger := golog.NewLogger(
golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.NewJSONWriterConfig(os.Stdout, nil),
),
)
// Create slog handler that uses golog
handler := goslog.Handler(gologLogger, goslog.ConvertDefaultLevels)
// Use with slog
logger := slog.New(handler)
logger.Info("Hello from slog", "key", "value")- Standard API: Use Go's standard library slog API
- Existing Code: Works with existing code that uses slog
- golog Features: Get all golog benefits (multiple writers, rotation, colors)
- Full Compatibility: Passes slogtest compliance suite
See the goslog package documentation for more details.
func loggingMiddleware(log *golog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Log request
log.Info("HTTP request").
Request(r).
Log()
next.ServeHTTP(w, r)
// Log response
log.Info("HTTP response").
Str("method", r.Method).
Str("path", r.URL.Path).
Duration("duration", time.Since(start)).
Log()
})
}
}colorizer := golog.NewStyledColorizer()
// or implement your own Colorizer interface
config := golog.NewConfig(
&golog.DefaultLevels,
golog.AllLevelsActive,
golog.NewTextWriterConfig(os.Stdout, nil, colorizer),
)defer func() {
if r := recover(); r != nil {
log.Error("Panic recovered").
CallStack("stack").
Any("panic_value", r).
Log()
}
}()Use the golog:"redact" struct tag to automatically redact sensitive fields when logging structs:
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Password string `json:"password" golog:"redact"`
APIKey string `json:"api_key" golog:"redact"`
}
user := User{
ID: 123,
Username: "john_doe",
Password: "secret123",
APIKey: "sk-abc123",
}
log.Info("User logged in").StructFields(user).Log()
// Output: ... id=123 username="john_doe" password="***REDACTED***" api_key="***REDACTED***"The golog:"redact" tag works with both StructFields() and TaggedStructFields() methods.
customLevels := &golog.Levels{
Trace: -20,
Debug: -10,
Info: 0,
Warn: 10,
Error: 20,
Fatal: 30,
Names: map[golog.Level]string{
-20: "TRACE",
-10: "DEBUG",
0: "INFO",
10: "WARN",
20: "ERROR",
30: "FATAL",
},
}Log levels have numeric values where lower = more verbose, higher = more severe:
Trace=-20 < Debug=-10 < Info=0 < Warn=10 < Error=20 < Fatal=30
// FilterOutBelow filters out less severe levels (lower numeric values)
// Warn=10, so this filters out Trace=-20, Debug=-10, Info=0
// Result: only Warn, Error, Fatal are logged
filter := golog.DefaultLevels.Warn.FilterOutBelow()
// Filter out only DEBUG level (Debug=-10)
debugFilter := golog.DefaultLevels.Debug.FilterOut()
// Filter out everything except INFO (Info=0)
infoOnlyFilter := golog.DefaultLevels.Info.FilterOutAllOther()
// Combine multiple filters (filters are OR'd together)
// This filters out both Trace and Debug
combinedFilter := golog.JoinLevelFilters(
golog.DefaultLevels.Trace.FilterOut(),
golog.DefaultLevels.Debug.FilterOut(),
)golog is designed for high performance:
- Zero-allocation logging in most cases
- Object pooling for message and writer instances
- Efficient JSON encoding
- Lazy evaluation of expensive operations
- Minimal overhead for inactive log levels
Run internal benchmarks:
go test -bench=. -benchmemFor comparative benchmarks against other popular Go logging libraries (zerolog, zap, slog, logrus), the benchmarks are in a separate module to avoid adding those dependencies to the main golog module:
go test ./benchmarks -bench=. -benchmem -benchtime=1sSee benchmarks/README.md for detailed comparative analysis and performance insights.
golog is designed to strike a balance between performance and flexibility. While libraries like zerolog and zap prioritize raw speed, golog provides a richer feature set that makes it more adaptable to complex logging requirements.
| Feature | zerolog | zap | golog |
|---|---|---|---|
| Multi-writer support | Single output | Limited | Native, unlimited |
| Duplicate key prevention | No | No | Yes |
| Context attribute integration | Manual | Manual | Automatic |
| Sub-logger with inherited attributes | Basic | Basic | Full support with attrib recording |
| Zero allocations (simple message) | Yes | Yes | Yes |
| Zero allocations (with fields) | Yes | No (1 alloc) | Yes |
| slog compatibility | Separate adapter | Separate adapter | Native goslog package |
zerolog: Extreme Minimalism
- Optimized for a single use case: fast JSON logging to a single output
- Minimal abstraction layers result in the fastest raw JSON performance
- Disabled log levels have near-zero overhead (~5 ns/op)
- Trade-off: Limited flexibility for complex logging scenarios
- Trade-off: ConsoleWriter for text output is significantly slower (2363 ns/op vs 51 ns/op for JSON)
zap: Performance + Type Safety
- Typed
Fieldstructs provide compile-time safety - Separate "Sugar" logger offers convenience at the cost of performance
- Trade-off: Allocates a slice for variadic field arguments (~1 alloc per log call with fields)
golog: Flexibility + Features
- Native multi-writer architecture: Log to console, files, and external services simultaneously with different formats and filters per destination
- Automatic context integration: Attributes added to
context.Contextare automatically included in log messages without manual plumbing - Sub-logger attribute recording: The
With().SubLogger()pattern creates child loggers that efficiently inherit and extend parent attributes - Duplicate key prevention: Prevents accidental duplicate keys in log output, ensuring clean structured data
- Zero allocations for standard logging: Despite the richer feature set, golog achieves zero allocations for JSON logging with fields
- Nil-safe design: A nil logger is safe to use and won't panic, simplifying error handling
golog is the right choice when you need:
- Multiple output destinations: Log to stdout with colors for development and JSON files for production simultaneously
- Request-scoped logging: Automatically propagate correlation IDs, user IDs, and other context through your application
- Sub-loggers with inherited context: Create child loggers for specific components that include parent attributes
- Clean structured data: Prevent duplicate keys from appearing in your logs
- slog compatibility: Use golog as a backend for Go's standard library logging interface
- Rotating log files: Built-in support for size-based log rotation
- zerolog: When raw JSON logging speed is the only priority and you don't need multi-writer support, context integration, or text/console output (zerolog's ConsoleWriter is slow at 2363 ns/op)
- zap: When you prefer a variadic field API with compile-time type checking
- slog: When you want zero external dependencies and good-enough performance from the standard library
For most applications, the performance difference between logging libraries is negligible. At 250 ns/op for a simple JSON message, golog can handle 4 million log messages per second on a single core. With structured fields, golog (353 ns/op) is faster than zap (451 ns/op). With complex fields including error and time.Time, golog achieves 391 ns/op with zero allocations, outperforming zap's 475 ns/op with 1 allocation. The additional features golog provides—multi-writer support, context integration, and duplicate key prevention—often save more development time than the nanoseconds saved by faster alternatives.
Note on text output: golog is the fastest for text/console output at 394 ns/op with zero allocations. This outperforms zap (514 ns/op, 4 allocs), slog (677 ns/op, 3 allocs), and especially zerolog's ConsoleWriter (2363 ns/op with 46 allocations).
The performance gap for JSON output becomes meaningful only in extreme high-throughput scenarios (100K+ logs/second sustained), where zerolog's 51 ns/op provides measurable benefits. For typical applications, golog's flexibility and rich feature set make it a more productive choice.
- Logger: Main logging interface
- Message: Fluent message builder
- Config: Logger configuration
- WriterConfig: Output writer configuration
- Level: Log level type
- LevelFilter: Level filtering interface
- JSONWriter: Structured JSON output
- TextWriter: Human-readable text output
- CallbackWriter: Custom callback-based writer
- MultiWriter: Multiple writer composition
- NopWriter: No-operation writer for testing
For complete API documentation, see pkg.go.dev.
This project is licensed under the MIT License - see the LICENSE file for details.