Uber Zap
High-performance, structured, leveled logging library developed by Uber. Achieves 67 ns/op, 0 allocations performance with speed comparable to Zerolog. Surpasses Zerolog in customization capabilities, popular in Go ecosystem.
Library
Zap
Overview
Zap is developed as "Blazing fast, structured, leveled logging in Go" and is a high-performance logging library for Go applications. Developed and maintained by Uber, it achieves exceptional performance with minimal memory allocation. Supporting everything from microservices to resource-constrained environments, it handles both structured and plain text logging for all Go applications.
Details
Zap 1.27.0 remains the de facto standard logging library in the Go ecosystem as of 2025, with active development. Prioritizing high performance above all, it boasts speeds 4-10 times faster than competing libraries while minimizing garbage collection overhead. It supports readable console output for development environments and JSON structured output for production, providing dynamic log level control and a type-safe field system.
Key Features
- Exceptional Performance: Benchmark-proven speed, 4-10x faster than competing libraries
- Memory Efficiency: Allocation-free logging minimizes GC overhead
- Structured Logging: Native JSON output improves searchability and analysis
- Two Logger Types: High-performance Logger (type-safe) and user-friendly SugaredLogger
- Flexible Configuration: Configuration presets optimized for development and production environments
- Dynamic Level Control: Runtime log level changes and filtering
Pros and Cons
Pros
- Fastest logging library in Go ecosystem (benchmark proven)
- Exceptional memory efficiency through allocation-free design
- Excellent searchability and analysis through structured logging
- Stable maintenance and proven track record by Uber
- Flexible usage with two logger types (performance vs convenience)
- Rich ecosystem and active community
Cons
- API somewhat more complex compared to other logging libraries
- Type-safe Logger requires detailed type specifications
- Production configuration optimization requires deep understanding
- Limited support for older Go versions (latest 2 versions only)
- Need to manage error handling and Sync() calls
- High initial setup cost for complex configurations
References
Code Examples
Basic Setup
# Install Zap package
go get go.uber.org/zap
# Additional packages (as needed)
go get go.uber.org/zap/zapcore
go get gopkg.in/natefinch/lumberjack.v2 # For log rotation
Simple Logger Usage
package main
import (
"time"
"go.uber.org/zap"
)
func main() {
// Production logger (JSON output)
logger, _ := zap.NewProduction()
defer logger.Sync() // Always call Sync() before program termination
// Structured log output (type-safe)
url := "https://example.com"
logger.Info("failed to fetch URL",
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
// Use SugaredLogger (convenience-focused)
sugar := logger.Sugar()
// Structured log (key-value pairs)
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second,
)
// Printf-style logging
sugar.Infof("Failed to fetch URL: %s", url)
}
Development and Production Configuration
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
var logger *zap.Logger
// Environment-specific logger configuration
if os.Getenv("ENV") == "production" {
// Production configuration (JSON format)
logger, _ = zap.NewProduction()
} else {
// Development configuration (readable console output)
logger, _ = zap.NewDevelopment()
}
defer logger.Sync()
// Custom configuration example
config := zap.NewProductionConfig()
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
config.OutputPaths = []string{"stdout", "app.log"}
config.ErrorOutputPaths = []string{"stderr", "error.log"}
customLogger, _ := config.Build()
defer customLogger.Sync()
// Log level examples
logger.Debug("Debug message")
logger.Info("Info message")
logger.Warn("Warning message")
logger.Error("Error message")
logger.DPanic("Development panic message")
// Field-based logging
logger.Info("User action",
zap.String("user_id", "user123"),
zap.String("action", "login"),
zap.Time("timestamp", time.Now()),
zap.Bool("success", true),
)
}
Advanced Logger Configuration and Customization
package main
import (
"os"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
// Custom encoder configuration
encoderConfig := zapcore.EncoderConfig{
TimeKey: "timestamp",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.StringDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
// Log rotation configuration (using lumberjack)
w := zapcore.AddSync(&lumberjack.Logger{
Filename: "/var/log/myapp/app.log",
MaxSize: 500, // megabytes
MaxBackups: 3, // number of backups to retain
MaxAge: 28, // days
Compress: true, // compress
})
// Multiple output configuration
consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
fileEncoder := zapcore.NewJSONEncoder(encoderConfig)
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
zapcore.NewCore(fileEncoder, w, zapcore.InfoLevel),
)
// Create custom logger
logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
defer logger.Sync()
// Usage example
logger.Info("Application started",
zap.String("version", "1.0.0"),
zap.String("environment", os.Getenv("ENV")),
)
}
Web Application Integration
package main
import (
"context"
"net/http"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Logger setup for middleware
func setupLogger() *zap.Logger {
config := zap.NewProductionConfig()
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
logger, _ := config.Build()
return logger
}
// HTTP request logging middleware
func loggingMiddleware(logger *zap.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 information
logger.Info("HTTP request started",
zap.String("method", r.Method),
zap.String("url", r.URL.String()),
zap.String("remote_addr", r.RemoteAddr),
zap.String("user_agent", r.UserAgent()),
)
// Process request
next.ServeHTTP(w, r)
// Log response information
duration := time.Since(start)
logger.Info("HTTP request completed",
zap.String("method", r.Method),
zap.String("url", r.URL.String()),
zap.Duration("duration", duration),
zap.Int64("duration_ms", duration.Milliseconds()),
)
})
}
}
// Context-based logging
func handleUser(logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("user_id")
// Create user-specific logger
userLogger := logger.With(
zap.String("user_id", userID),
zap.String("request_id", generateRequestID()),
)
ctx := context.WithValue(r.Context(), "logger", userLogger)
r = r.WithContext(ctx)
userLogger.Info("Starting user info retrieval")
// Business logic
if userID == "" {
userLogger.Warn("User ID not specified")
http.Error(w, "User ID required", http.StatusBadRequest)
return
}
// Database operation simulation
time.Sleep(50 * time.Millisecond)
userLogger.Info("User info retrieval completed",
zap.String("status", "success"),
)
w.WriteHeader(http.StatusOK)
w.Write([]byte("User info retrieved"))
}
}
func generateRequestID() string {
return "req_" + time.Now().Format("20060102150405")
}
func main() {
logger := setupLogger()
defer logger.Sync()
mux := http.NewServeMux()
mux.Handle("/user", loggingMiddleware(logger)(handleUser(logger)))
logger.Info("Server started", zap.Int("port", 8080))
http.ListenAndServe(":8080", mux)
}
Error Handling and Performance Optimization
package main
import (
"context"
"errors"
"sync"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
// Performance-optimized logger configuration
config := zap.NewProductionConfig()
config.Sampling = &zap.SamplingConfig{
Initial: 100,
Thereafter: 100,
}
logger, _ := config.Build()
defer logger.Sync()
// Error handling example
if err := processData(logger); err != nil {
logger.Error("Data processing error",
zap.Error(err),
zap.String("operation", "processData"),
zap.Time("timestamp", time.Now()),
)
}
// High-frequency logging performance test
performanceTest(logger)
}
func processData(logger *zap.Logger) error {
logger.Info("Starting data processing")
// Error occurs during processing
if err := doSomething(); err != nil {
// Wrap error and add context information
logger.Error("Error occurred during processing",
zap.Error(err),
zap.String("step", "doSomething"),
zap.Int("retry_count", 0),
)
return err
}
logger.Info("Data processing completed")
return nil
}
func doSomething() error {
return errors.New("simulated error")
}
func performanceTest(logger *zap.Logger) {
// High-frequency logging performance test
start := time.Now()
const iterations = 100000
var wg sync.WaitGroup
wg.Add(iterations)
for i := 0; i < iterations; i++ {
go func(index int) {
defer wg.Done()
// Conditional logging (optimization with level check)
if logger.Core().Enabled(zapcore.DebugLevel) {
logger.Debug("Performance test",
zap.Int("iteration", index),
zap.String("test_type", "concurrent"),
)
}
if index%1000 == 0 {
logger.Info("Progress status",
zap.Int("completed", index),
zap.Float64("progress", float64(index)/float64(iterations)*100),
)
}
}(i)
}
wg.Wait()
duration := time.Since(start)
logger.Info("Performance test completed",
zap.Int("iterations", iterations),
zap.Duration("total_duration", duration),
zap.Float64("logs_per_second", float64(iterations)/duration.Seconds()),
)
}
Dynamic Log Level Control
package main
import (
"net/http"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type DynamicLogger struct {
*zap.Logger
level *zap.AtomicLevel
}
func NewDynamicLogger() *DynamicLogger {
level := zap.NewAtomicLevel()
level.SetLevel(zap.InfoLevel)
config := zap.NewProductionConfig()
config.Level = level
logger, _ := config.Build()
return &DynamicLogger{
Logger: logger,
level: &level,
}
}
func (dl *DynamicLogger) SetLevel(level zapcore.Level) {
dl.level.SetLevel(level)
dl.Info("Log level changed",
zap.String("new_level", level.String()),
)
}
func (dl *DynamicLogger) GetLevel() zapcore.Level {
return dl.level.Level()
}
// HTTP endpoint for log level control
func (dl *DynamicLogger) handleLevelChange(w http.ResponseWriter, r *http.Request) {
levelStr := r.URL.Query().Get("level")
var level zapcore.Level
if err := level.UnmarshalText([]byte(levelStr)); err != nil {
dl.Error("Invalid log level",
zap.String("level", levelStr),
zap.Error(err),
)
http.Error(w, "Invalid log level", http.StatusBadRequest)
return
}
dl.SetLevel(level)
w.WriteHeader(http.StatusOK)
w.Write([]byte("Log level updated to " + level.String()))
}
func main() {
dl := NewDynamicLogger()
defer dl.Sync()
// Dynamic level change test
dl.Debug("This is a debug message") // Not output in initial state
dl.Info("This is an info message") // Output
// Change level to debug
dl.SetLevel(zap.DebugLevel)
dl.Debug("This is a debug message") // Now output
dl.Info("This is an info message") // Still output
// Dynamic control via HTTP server
http.HandleFunc("/log-level", dl.handleLevelChange)
dl.Info("Dynamic log level control server started", zap.Int("port", 8080))
// http.ListenAndServe(":8080", nil) // Uncomment for actual server startup
}