6.3. Zap 日志库

6.3.1. Zap 概述

Zap 是 Uber 开发的高性能、结构化日志库,特点:

  • 极快:零内存分配的日志记录

  • 结构化:支持结构化日志

  • 灵活:可定制的编码器和输出

6.3.2. 安装

go get go.uber.org/zap

6.3.3. 基本用法

6.3.3.1. 快速开始

package main

import "go.uber.org/zap"

func main() {
    // 开发模式:可读性好,性能略低
    logger, _ := zap.NewDevelopment()
    defer logger.Sync()
    
    logger.Info("This is an info message",
        zap.String("key", "value"),
        zap.Int("count", 42),
    )
    
    // 生产模式:JSON 格式,性能最优
    logger, _ = zap.NewProduction()
    defer logger.Sync()
    
    logger.Info("Production log",
        zap.String("service", "myapp"),
    )
}

6.3.3.2. SugaredLogger(更方便)

logger, _ := zap.NewProduction()
sugar := logger.Sugar()
defer sugar.Sync()

// printf 风格
sugar.Infof("Processing request %s", requestID)

// 键值对风格
sugar.Infow("User logged in",
    "username", "alice",
    "ip", "192.168.1.1",
)

6.3.4. 日志级别

logger.Debug("Debug message")   // -1
logger.Info("Info message")     //  0
logger.Warn("Warning message")  //  1
logger.Error("Error message")   //  2
logger.DPanic("DPanic message") //  3 (开发模式会 panic)
logger.Panic("Panic message")   //  4 (会 panic)
logger.Fatal("Fatal message")   //  5 (会调用 os.Exit(1))

6.3.5. 自定义配置

cfg := zap.Config{
    Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
    Development: false,
    Encoding:    "json",  // 或 "console"
    EncoderConfig: zapcore.EncoderConfig{
        TimeKey:        "timestamp",
        LevelKey:       "level",
        NameKey:        "logger",
        CallerKey:      "caller",
        FunctionKey:    zapcore.OmitKey,
        MessageKey:     "message",
        StacktraceKey:  "stacktrace",
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     zapcore.ISO8601TimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller:   zapcore.ShortCallerEncoder,
    },
    OutputPaths:      []string{"stdout", "/var/log/myapp.log"},
    ErrorOutputPaths: []string{"stderr"},
}

logger, _ := cfg.Build()

6.3.6. 结构化字段

// 强类型字段(性能最好)
logger.Info("Request processed",
    zap.String("method", "GET"),
    zap.String("path", "/api/users"),
    zap.Int("status", 200),
    zap.Duration("latency", time.Millisecond*150),
    zap.Time("timestamp", time.Now()),
    zap.Any("headers", headers),
    zap.Error(err),
)

// 对象字段
type User struct {
    ID   int
    Name string
}

func (u User) MarshalLogObject(enc zapcore.ObjectEncoder) error {
    enc.AddInt("id", u.ID)
    enc.AddString("name", u.Name)
    return nil
}

logger.Info("User created", zap.Object("user", User{ID: 1, Name: "Alice"}))

6.3.7. 日志轮转

Zap 本身不支持日志轮转,需要配合 lumberjack:

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
)

func getLogWriter() zapcore.WriteSyncer {
    lumberJackLogger := &lumberjack.Logger{
        Filename:   "/var/log/myapp.log",
        MaxSize:    100,    // MB
        MaxBackups: 5,
        MaxAge:     30,     // days
        Compress:   true,
    }
    return zapcore.AddSync(lumberJackLogger)
}

func main() {
    writeSyncer := getLogWriter()
    encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
    
    core := zapcore.NewCore(encoder, writeSyncer, zapcore.InfoLevel)
    logger := zap.New(core, zap.AddCaller())
    
    logger.Info("Using lumberjack for log rotation")
}

6.3.8. 全局 Logger

// 设置全局 logger
logger, _ := zap.NewProduction()
zap.ReplaceGlobals(logger)

// 使用全局 logger
zap.L().Info("Using global logger")
zap.S().Infof("Using global sugar logger: %s", message)

6.3.9. 添加上下文

// 创建带有固定字段的 logger
logger := zap.NewExample()
contextLogger := logger.With(
    zap.String("service", "user-service"),
    zap.String("version", "1.0.0"),
)

contextLogger.Info("Request received") // 自动包含 service 和 version

6.3.10. ⚠️ 常见陷阱

6.3.10.1. 陷阱 1:忘记 Sync

// ❌ 日志可能丢失
func main() {
    logger, _ := zap.NewProduction()
    logger.Info("Hello")
    // 程序退出,缓冲区未刷新
}

// ✅ 确保 Sync
func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()  // 刷新缓冲区
    logger.Info("Hello")
}

6.3.10.2. 陷阱 2:性能敏感场景使用 Sugar

// ❌ Sugar 稍慢(但仍然很快)
sugar.Infof("User %s logged in", username)

// ✅ 极致性能使用强类型
logger.Info("User logged in", zap.String("username", username))

6.3.10.3. 陷阱 3:错误处理

// ❌ Error 字段传 nil
logger.Error("Failed", zap.Error(nil))  // 输出 error: null

// ✅ 检查 error
if err != nil {
    logger.Error("Failed", zap.Error(err))
}

6.3.11. 与 Gin 集成

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

func GinZapLogger(logger *zap.Logger) gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        
        c.Next()
        
        logger.Info("Request",
            zap.String("method", c.Request.Method),
            zap.String("path", path),
            zap.Int("status", c.Writer.Status()),
            zap.Duration("latency", time.Since(start)),
            zap.String("client_ip", c.ClientIP()),
        )
    }
}

func main() {
    logger, _ := zap.NewProduction()
    
    r := gin.New()
    r.Use(GinZapLogger(logger))
    // ...
}

6.3.12. 参考资源