Zerolog
Fastest structured logging framework in Go. Achieves excellent performance of 35 ns/op with 0 allocations. Adopted by companies like CrowdStrike, recording highest speeds in almost all benchmark scenarios.
Library
Zerolog
Overview
Zerolog is a zero-allocation JSON logger for Go that achieves ultra-fast log processing and minimal memory usage as a structured logging library. With its JSON-first approach providing excellent machine readability and intuitive operation through a chaining API, it has become the standard logging solution widely adopted in high-load systems and microservice environments, boasting the highest level of performance in the Go ecosystem as of 2025.
Details
Zerolog 2025 edition achieves 5-10x faster log processing compared to traditional solutions through zero-allocation design, minimizing CPU overhead. It provides complete integration with monitoring tools like ELK Stack, Prometheus, and Grafana through JSON-only output, context-aware sub-logger functionality, custom field marshalers, and extensibility through hook functionality. With CBOR (Concise Binary Object Representation) binary encoding support, it achieves further acceleration and size reduction, optimized for massive log processing in distributed systems.
Key Features
- Zero-Allocation Design: Ultra-fast processing with minimized memory allocations
- JSON-First Output: Complete JSON format for machine-readable logs
- Chaining API: Intuitive and readable fluent interface
- Level-Based Sampling: Log volume control functionality during high load
- Context Integration: Complete integration with Go Context
- CBOR Support: Further acceleration through binary encoding
Pros and Cons
Pros
- Highest level performance in Go ecosystem (5-10x faster)
- Memory efficiency through zero-allocation design
- High compatibility with monitoring and analysis tools through JSON output
- Simple and intuitive chaining API
- Easy tracing in distributed systems through context support
- Further performance improvement and size reduction with CBOR support
- Lightweight with minimal external dependencies
Cons
- Low direct human readability due to JSON-only format
- Limited feature richness compared to Logrus and Zap
- Additional configuration required for environments needing plain text output
- Limited complex formatter and hook functionality
- ConsoleWriter required for readability during debugging
- Fewer learning resources compared to other loggers
Reference Pages
Usage Examples
Installation and Basic Setup
# Add zerolog to Go module
go get -u github.com/rs/zerolog/log
# For CBOR binary encoding
go build -tags binary_log .
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// UNIX timestamps (fast and lightweight)
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
// Basic log output
log.Print("hello world")
log.Info().Msg("Information message")
log.Debug().Msg("Debug message")
log.Warn().Msg("Warning message")
log.Error().Msg("Error message")
// Structured logging (recommended)
log.Info().
Str("user_id", "12345").
Str("action", "login").
Str("ip", "192.168.1.100").
Msg("User login")
// Multi-field logging
log.Debug().
Str("Scale", "833 cents").
Float64("Interval", 833.09).
Msg("Fibonacci is everywhere")
// Fields only without message
log.Debug().
Str("Name", "Tom").
Send()
}
// Output examples:
// {"time":1516134303,"level":"debug","message":"hello world"}
// {"level":"info","time":1494567715,"user_id":"12345","action":"login","ip":"192.168.1.100","message":"User login"}
// {"level":"debug","Scale":"833 cents","Interval":833.09,"time":1562212768,"message":"Fibonacci is everywhere"}
// {"level":"debug","Name":"Tom","time":1562212768}
Log Level Control and Configuration Management
package main
import (
"flag"
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func init() {
// Global configuration customization
zerolog.TimestampFieldName = "timestamp"
zerolog.LevelFieldName = "severity"
zerolog.MessageFieldName = "message"
zerolog.ErrorFieldName = "error"
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
// Duration field unit configuration
zerolog.DurationFieldUnit = time.Millisecond
zerolog.DurationFieldInteger = true
}
func main() {
// Command-line flag level control
debug := flag.Bool("debug", false, "Enable debug level logging")
flag.Parse()
// Default level configuration
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if *debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
// Environment variable configuration
if os.Getenv("LOG_LEVEL") == "trace" {
zerolog.SetGlobalLevel(zerolog.TraceLevel)
}
// Usage examples for all log levels
log.Trace().Msg("Trace level (most detailed)")
log.Debug().Msg("Debug level (development only)")
log.Info().Msg("Info level (normal operation)")
log.Warn().Msg("Warning level (attention needed)")
log.Error().Msg("Error level (error occurred)")
// log.Fatal().Msg("Fatal level (program termination)")
// log.Panic().Msg("Panic level (panic() execution)")
// Conditional logging (performance optimization)
if e := log.Debug(); e.Enabled() {
// Expensive operation executed only when debug level is enabled
expensiveValue := performExpensiveOperation()
e.Str("result", expensiveValue).Msg("Expensive operation result")
}
// Disable logging completely
// zerolog.SetGlobalLevel(zerolog.Disabled)
}
func performExpensiveOperation() string {
// Simulation of computationally expensive operation
return "expensive_result"
}
Custom Loggers and Context Management
package main
import (
"context"
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// Create custom logger instance
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
// Add persistent context to global logger
log.Logger = log.With().
Str("service", "user-service").
Str("version", "v1.2.3").
Logger()
// Create sub-loggers (by component)
dbLogger := log.With().
Str("component", "database").
Logger()
apiLogger := log.With().
Str("component", "api").
Logger()
// Context-aware logger
requestLogger := log.With().
Str("request_id", "req-123456").
Str("user_id", "user-789").
Logger()
// Basic log output
logger.Info().Str("foo", "bar").Msg("hello world")
// Component-specific logs
dbLogger.Info().
Str("operation", "SELECT").
Str("table", "users").
Dur("duration", 45*time.Millisecond).
Msg("Database operation completed")
apiLogger.Warn().
Int("status_code", 429).
Str("endpoint", "/api/users").
Msg("Rate limit reached")
// Request-specific logs
requestLogger.Info().Msg("Request processing started")
requestLogger.Debug().Str("method", "POST").Msg("HTTP method verification")
requestLogger.Info().Msg("Request processing completed")
// Go Context integration
ctx := context.Background()
// Embed logger in context
ctx = logger.WithContext(ctx)
// Pass context to other functions
processRequest(ctx)
}
func processRequest(ctx context.Context) {
// Retrieve logger from context
logger := zerolog.Ctx(ctx)
logger.Info().
Str("function", "processRequest").
Msg("Executing request processing function")
// Additional context information
logger.Debug().
Str("trace_id", "trace-abc123").
Str("span_id", "span-def456").
Msg("Distributed tracing information")
}
Advanced Field Types and Dictionary Structures
package main
import (
"errors"
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func main() {
// Standard type fields
log.Info().
Str("string_field", "string value").
Bool("bool_field", true).
Int("int_field", 123).
Int8("int8_field", 8).
Int16("int16_field", 16).
Int32("int32_field", 32).
Int64("int64_field", 64).
Uint("uint_field", 456).
Uint8("uint8_field", 8).
Uint16("uint16_field", 16).
Uint32("uint32_field", 32).
Uint64("uint64_field", 64).
Float32("float32_field", 3.14).
Float64("float64_field", 2.718281828).
Msg("Standard type fields example")
// Advanced field types
err := errors.New("sample error")
now := time.Now()
duration := 150 * time.Millisecond
log.Error().
Err(err). // Error field
Time("event_time", now). // Time field
Dur("response_time", duration). // Duration field
Timestamp(). // Auto-add timestamp
Hex("binary_data", []byte{0xde, 0xad, 0xbe, 0xef}). // Binary data (hex)
RawJSON("raw_json", []byte(`{"key":"value"}`)). // Raw JSON
Msg("Advanced field types example")
// Slice type fields
log.Info().
Strs("string_array", []string{"a", "b", "c"}).
Ints("int_array", []int{1, 2, 3}).
Bools("bool_array", []bool{true, false, true}).
Errs("error_array", []error{
errors.New("error 1"),
errors.New("error 2"),
}).
Msg("Slice type fields example")
// Dictionary structure (nested objects)
log.Info().
Str("service", "user-management").
Dict("user", zerolog.Dict().
Str("name", "John Doe").
Int("age", 30).
Str("email", "[email protected]").
Dict("address", zerolog.Dict().
Str("country", "USA").
Str("city", "New York").
Str("postal_code", "10001"),
),
).
Dict("request", zerolog.Dict().
Str("method", "POST").
Str("path", "/api/users").
Int("status_code", 201).
Dur("duration", 123*time.Millisecond),
).
Msg("User creation completed")
// Interface type (using reflection)
complexData := map[string]interface{}{
"metadata": map[string]interface{}{
"version": "1.0",
"tags": []string{"production", "api"},
},
"metrics": map[string]interface{}{
"cpu_usage": 85.5,
"memory_usage": 1024,
},
}
log.Debug().
Interface("complex_data", complexData).
Msg("Complex data structure log")
// Dynamic fields via function
log.Info().
Func(func(e *zerolog.Event) {
// This function is executed only when log level is enabled
systemInfo := getSystemInfo()
e.Str("hostname", systemInfo.Hostname)
e.Float64("cpu_load", systemInfo.CPULoad)
e.Int64("memory_free", systemInfo.MemoryFree)
}).
Msg("System information")
}
type SystemInfo struct {
Hostname string
CPULoad float64
MemoryFree int64
}
func getSystemInfo() SystemInfo {
// System information retrieval implementation (simplified)
return SystemInfo{
Hostname: "web-server-01",
CPULoad: 23.5,
MemoryFree: 2048 * 1024 * 1024,
}
}
ConsoleWriter and Output Customization
package main
import (
"os"
"strings"
"fmt"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func setupDevelopmentLogger() {
// Development environment: human-readable output
output := zerolog.ConsoleWriter{Out: os.Stderr}
log.Logger = log.Output(output)
}
func setupCustomConsoleWriter() {
// Custom format configuration
output := zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: time.RFC3339,
NoColor: false, // Enable color output
}
// Level display customization
output.FormatLevel = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
}
// Message display customization
output.FormatMessage = func(i interface{}) string {
return fmt.Sprintf("***%s***", i)
}
// Field name customization
output.FormatFieldName = func(i interface{}) string {
return fmt.Sprintf("%s:", i)
}
// Field value customization
output.FormatFieldValue = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("%s", i))
}
logger := zerolog.New(output).With().Timestamp().Logger()
logger.Info().Str("foo", "bar").Msg("Hello World")
// Output example: 2006-01-02T15:04:05Z07:00 | INFO | ***Hello World*** foo:BAR
}
func setupAdvancedConsoleWriter() {
// Advanced customization: field order control
output := zerolog.ConsoleWriter{
Out: os.Stdout,
NoColor: true,
PartsOrder: []string{
"level", "service", "component", "request_id", "message",
},
FieldsExclude: []string{"service", "component", "request_id"},
}
// Part-specific value formatting
output.FormatPartValueByName = func(i interface{}, s string) string {
var ret string
switch s {
case "service":
ret = strings.ToUpper(fmt.Sprintf("[%s]", i))
case "component":
ret = strings.ToLower(fmt.Sprintf("(%s)", i))
case "request_id":
ret = fmt.Sprintf("req:%s", i)
default:
ret = fmt.Sprintf("%s", i)
}
return ret
}
logger := zerolog.New(output)
logger.Info().
Str("service", "api").
Str("component", "auth").
Str("request_id", "123").
Str("user", "john").
Msg("Authentication successful")
// Output example: INFO [API] (auth) req:123 Authentication successful user:john
}
func setupMultipleOutputs() {
// Multiple output destinations
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
// JSON (file) + human-readable (console)
multi := zerolog.MultiLevelWriter(consoleWriter, os.Stderr)
logger := zerolog.New(multi).With().Timestamp().Logger()
logger.Info().Msg("Hello World!")
// Output:
// Line 1 (Console): 12:36PM INF Hello World!
// Line 2 (Stderr): {"level":"info","time":"2019-11-07T12:36:38+03:00","message":"Hello World!"}
}
func main() {
// Output switching by environment
env := os.Getenv("ENVIRONMENT")
switch env {
case "development":
setupDevelopmentLogger()
case "testing":
setupCustomConsoleWriter()
case "staging":
setupAdvancedConsoleWriter()
case "demo":
setupMultipleOutputs()
default:
// Production environment: JSON output
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
}
// Log output test
log.Info().Str("foo", "bar").Msg("Test message")
log.Warn().Int("code", 404).Msg("Resource not found")
log.Error().Err(errors.New("test error")).Msg("An error occurred")
}
Sampling and Performance Optimization
package main
import (
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func basicSampling() {
// Basic sampling: log output once every 10 times
sampled := log.Sample(&zerolog.BasicSampler{N: 10})
// Loop 100 times, only 10 logs will be output
for i := 0; i < 100; i++ {
sampled.Info().Int("iteration", i).Msg("Sampled message")
}
}
func advancedSampling() {
// Level-based sampling configuration
sampled := log.Sample(zerolog.LevelSampler{
// Debug level: Allow burst of 5 per second, then 1 in 100
DebugSampler: &zerolog.BurstSampler{
Burst: 5,
Period: 1 * time.Second,
NextSampler: &zerolog.BasicSampler{N: 100},
},
// Info level: No sampling (output all)
InfoSampler: nil,
// Error level: No sampling (output all)
ErrorSampler: nil,
})
// High-frequency debug log example
for i := 0; i < 1000; i++ {
sampled.Debug().Int("loop", i).Msg("High-frequency debug")
sampled.Info().Int("important", i).Msg("Important information")
if i%100 == 0 {
sampled.Error().Int("checkpoint", i).Msg("Checkpoint")
}
time.Sleep(10 * time.Millisecond)
}
}
func disableSampling() {
// Dynamic sampling disable
zerolog.DisableSampling(true)
// This makes all sampled loggers output 100% of events
sampled := log.Sample(&zerolog.BasicSampler{N: 10})
for i := 0; i < 20; i++ {
sampled.Info().Int("all_logged", i).Msg("All logs output")
}
// Re-enable sampling
zerolog.DisableSampling(false)
}
func performanceOptimizedLogging() {
// Performance optimization settings
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix // UNIX time (fastest)
zerolog.DurationFieldUnit = time.Millisecond // Duration unit
zerolog.DurationFieldInteger = true // Output duration as integer
// High-speed logger configuration
logger := zerolog.New(os.Stdout)
// High-speed log output for benchmarking
start := time.Now()
for i := 0; i < 10000; i++ {
logger.Info().
Int("iteration", i).
Str("operation", "benchmark").
Dur("elapsed", time.Since(start)).
Msg("High-speed log test")
}
total := time.Since(start)
logger.Info().
Int("total_logs", 10000).
Dur("total_time", total).
Float64("logs_per_second", float64(10000)/total.Seconds()).
Msg("Benchmark completed")
}
func main() {
log.Info().Msg("=== Basic Sampling ===")
basicSampling()
log.Info().Msg("=== Advanced Sampling ===")
advancedSampling()
log.Info().Msg("=== Disable Sampling ===")
disableSampling()
log.Info().Msg("=== Performance Optimization ===")
performanceOptimizedLogging()
}
Hooks and Error Handling
package main
import (
"errors"
"fmt"
"context"
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/pkg/errors"
)
// Custom hook: Add severity field
type SeverityHook struct{}
func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
if level != zerolog.NoLevel {
e.Str("severity", level.String())
}
}
// Tracing hook: Add distributed tracing information
type TracingHook struct{}
func (h TracingHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
ctx := e.GetCtx()
if ctx != nil {
if spanID := getSpanIDFromContext(ctx); spanID != "" {
e.Str("span_id", spanID)
}
if traceID := getTraceIDFromContext(ctx); traceID != "" {
e.Str("trace_id", traceID)
}
}
}
func getSpanIDFromContext(ctx context.Context) string {
// Get Span ID from actual tracing library
// Simplified here
return "span-12345"
}
func getTraceIDFromContext(ctx context.Context) string {
// Get Trace ID from actual tracing library
// Simplified here
return "trace-abcdef"
}
func setupErrorStackTrace() {
// Error log with stack trace
zerolog.ErrorStackMarshaler = func(err error) interface{} {
// Integration with github.com/pkg/errors
type stackTracer interface {
StackTrace() errors.StackTrace
}
if err, ok := err.(stackTracer); ok {
return err.StackTrace()
}
return nil
}
}
func demonstrateErrorLogging() {
// Basic error log
err := errors.New("database connection error")
log.Error().Err(err).Msg("Error occurred")
// Error with stack trace
err = errors.Wrap(errors.New("internal error"), "error in external processing")
log.Error().Stack().Err(err).Msg("Error with stack trace")
}
func demonstrateHooks() {
// Create logger with hooks
logger := log.Hook(SeverityHook{}).Hook(TracingHook{})
ctx := context.Background()
// Hooked logs
logger.Info().Ctx(ctx).Msg("Hook functionality test")
logger.Warn().Ctx(ctx).Msg("Warning message")
logger.Error().Ctx(ctx).Msg("Error message")
}
func setupErrorHandler() {
// Custom error handler configuration
zerolog.ErrorHandler = func(err error) {
fmt.Fprintf(os.Stderr, "Zerolog write error: %v\n", err)
}
}
func demonstrateCallerInfo() {
// Caller information configuration
// Short format (filename:line)
zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
}
// Logger with caller information
logger := log.With().Caller().Logger()
logger.Info().Msg("Log with caller information")
// Output example: {"level":"info","caller":"main.go:123","message":"Log with caller information"}
// Full path format
zerolog.CallerMarshalFunc = nil // Reset to default
logger = log.With().Caller().Logger()
logger.Info().Msg("Full path caller information")
// Output example: {"level":"info","caller":"/go/src/project/main.go:126","message":"Full path caller information"}
}
func businessLogicWithLogging(ctx context.Context) error {
logger := zerolog.Ctx(ctx)
logger.Info().Msg("Business logic started")
// Error occurs during some processing
if err := performDatabaseOperation(); err != nil {
logger.Error().
Err(err).
Str("operation", "database_query").
Msg("Error in database operation")
return errors.Wrap(err, "error in business logic")
}
logger.Info().Msg("Business logic completed")
return nil
}
func performDatabaseOperation() error {
// Database operation simulation
return errors.New("connection timeout")
}
func main() {
// Initial setup
setupErrorStackTrace()
setupErrorHandler()
// Hook functionality demo
log.Info().Msg("=== Hook Functionality Demo ===")
demonstrateHooks()
// Error log demo
log.Info().Msg("=== Error Log Demo ===")
demonstrateErrorLogging()
// Caller information demo
log.Info().Msg("=== Caller Information Demo ===")
demonstrateCallerInfo()
// Context-aware business logic demo
log.Info().Msg("=== Business Logic Demo ===")
ctx := log.With().
Str("request_id", "req-789").
Str("user_id", "user-456").
Logger().WithContext(context.Background())
if err := businessLogicWithLogging(ctx); err != nil {
log.Error().Err(err).Msg("Business logic error")
}
}