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.

Logging LibraryGoGolangZero AllocationJSONHigh PerformanceStructured Logging

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")
	}
}