Zap — 高性能结构化日志
Zap 是 Uber 开源的 Go 日志库,以极低的内存分配和极高的性能著称,是生产环境的首选。
安装
bash
go get -u go.uber.org/zap两种 Logger
go
// 1. zap.Logger — 零分配,性能最高,但 API 稍繁琐
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("用户登录",
zap.String("username", "alice"),
zap.Int("userID", 42),
zap.Duration("latency", time.Millisecond*100),
)
// 2. zap.SugaredLogger — 略有分配,但 API 更友好
sugar := logger.Sugar()
sugar.Infow("用户登录",
"username", "alice",
"userID", 42,
)
sugar.Infof("用户 %s 登录成功", "alice")配置
go
// 开发环境(彩色输出,人类可读)
logger, _ := zap.NewDevelopment()
// 生产环境(JSON 格式,高性能)
logger, _ := zap.NewProduction()
// 自定义配置
config := zap.Config{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
Development: false,
Encoding: "json", // "json" 或 "console"
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "ts",
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", "/var/log/app.log"},
ErrorOutputPaths: []string{"stderr"},
}
logger, err := config.Build()结构化字段
go
// 强类型字段(零分配)
logger.Info("处理请求",
zap.String("method", "GET"),
zap.String("path", "/api/users"),
zap.Int("status", 200),
zap.Float64("latency_ms", 12.5),
zap.Bool("cached", true),
zap.Time("timestamp", time.Now()),
zap.Duration("elapsed", time.Millisecond*12),
zap.Error(err),
zap.Strings("tags", []string{"api", "v1"}),
zap.Any("user", user), // 任意类型(有反射开销)
)
// 对象字段(实现 zap.ObjectMarshaler 接口)
type UserLog struct {
ID int
Name string
Email string
}
func (u UserLog) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddInt("id", u.ID)
enc.AddString("name", u.Name)
enc.AddString("email", u.Email)
return nil
}
logger.Info("用户操作", zap.Object("user", UserLog{1, "Alice", "alice@example.com"}))With — 创建带固定字段的子 Logger
go
// 为每个请求创建带 requestID 的 logger
func handleRequest(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
log := logger.With(
zap.String("request_id", requestID),
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
)
log.Info("请求开始")
user, err := getUser(r.Context())
if err != nil {
log.Error("获取用户失败", zap.Error(err))
return
}
log.Info("请求完成", zap.Int("user_id", user.ID))
}动态日志级别
go
// 运行时动态调整日志级别(无需重启)
atom := zap.NewAtomicLevel()
logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
zapcore.Lock(os.Stdout),
atom,
))
// HTTP 接口动态调整级别
http.HandleFunc("/log/level", atom.ServeHTTP)
// curl -X PUT http://localhost:8080/log/level -d '{"level":"debug"}'多输出目标
go
// 同时输出到控制台和文件
fileEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
logFile, _ := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
core := zapcore.NewTee(
zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zap.InfoLevel),
zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zap.DebugLevel),
)
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel))日志轮转(配合 lumberjack)
bash
go get gopkg.in/natefinish/lumberjack.v2go
import "gopkg.in/natefinish/lumberjack.v2"
w := zapcore.AddSync(&lumberjack.Logger{
Filename: "/var/log/app.log",
MaxSize: 100, // MB
MaxBackups: 7, // 保留 7 个备份
MaxAge: 30, // 保留 30 天
Compress: true, // 压缩旧日志
})
core := zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
w,
zap.InfoLevel,
)
logger := zap.New(core)Gin 集成
go
// Gin 使用 Zap 中间件
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
logger.Info("HTTP请求",
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.Duration("cost", cost),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
)
}
}
func GinRecovery(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),
zap.ByteString("stack", debug.Stack()),
)
c.AbortWithStatus(500)
}
}()
c.Next()
}
}全局 Logger
go
// 初始化全局 logger
func InitLogger(env string) {
var logger *zap.Logger
if env == "production" {
logger, _ = zap.NewProduction()
} else {
logger, _ = zap.NewDevelopment()
}
zap.ReplaceGlobals(logger)
}
// 在任何地方使用
zap.L().Info("全局 logger")
zap.S().Infow("Sugar 全局 logger", "key", "value")Zap vs Zerolog vs slog
- Zap:功能最全,生态最好,适合大型项目
- Zerolog:API 更简洁,性能相当,适合中小项目
- slog(标准库,Go 1.21+):无需第三方依赖,适合库开发
生产项目推荐 Zap,库开发推荐 slog。