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))
// ...
}