← Viper | Validator →

Zap - Go 高性能日志库

Zap 是 Uber 开源的高性能日志库,以极致的性能和丰富的功能著称。掌握 Zap 是开发生产级 Go 应用的基础,特别适用于高并发、高性能场景。

📌 核心概念

高性能

零分配日志

10x 性能
📝

结构化

JSON/Console 输出

Structured
🎯

日志级别

Debug 到 Fatal

LogLevel
🔧

字段添加

类型安全字段

Fields

快速开始

📝 基础使用

package main

import (
    "go.uber.org/zap"
)

func main() {
    // 方式 1: 快速使用 (适合原型开发)
    logger, _ := zap.NewProduction()
    defer logger.Sync() // 刷新缓冲
    
    logger.Info("Server started",
        zap.String("host", "localhost"),
        zap.Int("port", 8080),
    )
    
    logger.Error("Failed to connect",
        zap.String("error", "connection refused"),
    )
    
    // 方式 2: 便捷 SugaredLogger (性能略低)
    sugar := logger.Sugar()
    sugar.Infow("Request received",
        "method", "GET",
        "path", "/api/users",
    )
    sugar.Errorw("Invalid input", "error", "missing field")
}

💡 Zap 要点

  • Logger: 类型安全,性能最高
  • SugaredLogger: 使用灵活,性能略低
  • Sync(): 刷新缓冲,确保日志写入
  • 字段类型: zap.String/Int/Bool 等类型安全

日志级别

📝 日志级别使用

package main

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    
    // Debug: 调试信息
    logger.Debug("Debug message", zap.String("detail", "verbose"))
    
    // Info: 一般信息
    logger.Info("Server started", zap.Int("port", 8080))
    
    // Warn: 警告信息
    logger.Warn("Deprecated API used", zap.String("api", "v1"))
    
    // Error: 错误信息
    logger.Error("Database connection failed", zap.Error(err))
    
    // DPanic: 开发环境 panic
    logger.DPanic("Unexpected state")
    
    // Panic: 记录日志并 panic
    // logger.Panic("Critical error")
    
    // Fatal: 记录日志并退出程序
    // logger.Fatal("Unrecoverable error")
    
    // 检查级别再记录 (避免不必要的参数计算)
    if logger.Core().Enabled(zapcore.DebugLevel) {
        logger.Debug("Expensive debug", zap.Any("data", compute()))
    }
}

func compute() string {
    return "computed"
}

配置日志

📝 自定义配置

package main

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

func NewLogger() (*zap.Logger, error) {
    // 配置结构
    config := zap.Config{
        Level:       zap.NewAtomicLevelAt(zapcore.InfoLevel),
        Development: false,
        Encoding:    "json", // json 或 console
        EncoderConfig: zapcore.EncoderConfig{
            TimeKey:        "time",
            LevelKey:       "level",
            NameKey:        "logger",
            CallerKey:      "caller",
            MessageKey:     "msg",
            StacktraceKey:  "stacktrace",
            LineEnding:     zapcore.DefaultLineEnding,
            EncodeLevel:    zapcore.LowercaseLevelEncoder,
            EncodeTime:     zapcore.ISO8601TimeEncoder,
            EncodeDuration: zapcore.SecondsDurationEncoder,
            EncodeCaller:   zapcore.ShortCallerEncoder,
        },
        OutputPaths:      []string{"stdout", "./logs/app.log"},
        ErrorOutputPaths: []string{"stderr"},
    }
    
    return config.Build()
}

func main() {
    logger, err := NewLogger()
    if err != nil {
        panic(err)
    }
    defer logger.Sync()
    
    logger.Info("Application started")
}

📝 开发环境配置

func NewDevLogger() (*zap.Logger, error) {
    config := zap.NewDevelopmentConfig()
    
    // 自定义输出
    config.OutputPaths = []string{"stdout"}
    
    // 使用 console 编码器
    config.Encoding = "console"
    
    // 启用调用者信息
    config.EncoderConfig.CallerKey = "caller"
    
    return config.Build()
}

字段添加

📝 类型安全字段

func handleRequest(logger *zap.Logger) {
    // 基本类型字段
    logger.Info("Request",
        zap.String("method", "GET"),
        zap.String("path", "/api/users"),
        zap.Int("status", 200),
        zap.Int64("duration_ms", 150),
        zap.Bool("success", true),
        zap.Float64("size_kb", 1.5),
    )
    
    // 复杂类型字段
    logger.Info("User data",
        zap.Any("user", user),
        zap.Strings("tags", []string{"vip", "active"}),
        zap.Ints("ids", []int{1, 2, 3}),
        zap.Errors([]error{err1, err2}),
    )
    
    // 错误字段
    if err != nil {
        logger.Error("Operation failed", zap.Error(err))
    }
    
    // 命名空间 (字段分组)
    logger.Info("Request",
        zap.Namespace("request"),
        zap.String("method", "POST"),
        zap.Namespace("response"),
        zap.Int("status", 201),
    )
}

Logger 组合

📝 With 和 Named

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()
    
    // With: 添加公共字段
    requestLogger := logger.With(
        zap.String("request_id", "abc123"),
        zap.String("client_ip", "192.168.1.1"),
    )
    
    requestLogger.Info("Request started")
    requestLogger.Info("Request completed")
    
    // Named: 添加 logger 名称
    dbLogger := logger.Named("database")
    dbLogger.Info("Connected to database")
    
    // 组合使用
    userServiceLogger := logger.Named("service").With(
        zap.String("service", "user"),
    )
    userServiceLogger.Info("User created", zap.Int64("user_id", 123))
}

Hook 和扩展

📝 日志 Hook

package main

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

// 自定义 Hook: 错误日志发送告警
type ErrorHook struct{}

func (h *ErrorHook) Write(p []byte) (int, error) {
    // 发送告警通知
    sendAlert(string(p))
    return len(p), nil
}

func NewLoggerWithHook() (*zap.Logger, error) {
    config := zap.NewProductionConfig()
    
    // 添加 Hook: Error 级别以上触发
    config.Hooks = []zapcore.Core{
        zapcore.NewCore(
            zapcore.NewJSONEncoder(config.EncoderConfig),
            &ErrorHook{},
            zapcore.ErrorLevel,
        ),
    }
    
    return config.Build()
}

func sendAlert(msg string) {
    // 发送告警到钉钉/企业微信/Slack 等
}

与 Gin 集成

📝 Gin + Zap 中间件

package middleware

import (
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
    "time"
)

func Logger(logger *zap.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        // 处理请求
        c.Next()
        
        // 记录日志
        logger.Info("Request",
            zap.String("method", c.Request.Method),
            zap.String("path", c.Request.URL.Path),
            zap.Int("status", c.Writer.Status()),
            zap.String("client_ip", c.ClientIP()),
            zap.Duration("duration", time.Since(start)),
        )
    }
}

func Recovery(logger *zap.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                logger.Error("Panic recovered",
                    zap.Any("error", err),
                    zap.String("path", c.Request.URL.Path),
                )
                c.AbortWithStatus(500)
            }
        }()
        c.Next()
    }
}

最佳实践

✅ Zap 使用建议

  • 全局 Logger: 使用全局变量或注入到 Context
  • 延迟 Sync: 使用 defer logger.Sync()
  • 类型安全: 优先使用 Logger 而非 SugaredLogger
  • 字段复用: 使用 With 添加公共字段
  • 级别检查: 使用 Enabled 避免不必要的计算
  • 结构化输出: 生产环境使用 JSON 格式

🚨 常见陷阱

  • 忘记 Sync: 日志可能丢失,程序退出前必须 Sync
  • 过度使用 Any: 失去类型安全,性能下降
  • Debug 生产: 生产环境应设置为 Info 或 Warn
  • 敏感信息: 避免记录密码、token 等敏感数据
  • 高频日志: 避免在循环中记录大量日志