Zap 是 Uber 开源的一款结构化分级极致性能优化的 Go 日志库。它专为性能敏感的场景设计,支持强类型字段高效序列化,并兼顾开发体验。

Zap 提供了两种日志 API:

  • Logger:强类型结构化 API,性能最优;
  • SugaredLogger:支持 fmt.Sprintf 风格,使用更便捷,性能略低;

1 为什么 Zap 如此高性能?

1.1 避免反射开销

  • 传统库如 Logrus:大量使用 interface{} 与反射;
  • Zap:使用强类型字段(如 zap.String、zap.Int),绕过反射,性能更优。

1.2 减少内存分配

  • 使用 sync.Pool 重用对象,缓解 GC 压力;
  • 临时缓冲区、日志字段结构都支持复用;
  • 零分配字符串编码,避免频繁拼接。

1.3 精简调用链

  • 其他库:多层接口封装(Logger → Encoder → Formatter);
  • Zap:扁平化设计,Logger → Encoder,直达底层。

1.4 非阻塞写入 & 异步优化

  • 日志调用方不直接执行 I/O;
  • 通过 channel 实现生产者 - 消费者模型,避免阻塞;
  • 默认同步写入,结合 WriteSyncer 可实现批量输出。

1.5 编码器优化

  • 高性能 JSON / Console 编码器;
  • 直接写入 []byte,绕过字符串中间态;
  • 支持颜色、高亮、字段定制等扩展性良好的格式化能力。

1.6 高性能 API 设计

  • 明确区分性能优先(Logger)与开发便捷(SugaredLogger)两条路径;
  • 鼓励在核心路径中使用 Logger,在外围使用 SugaredLogger。

2 结构化日志

传统日志多为文本拼接,机器难以解析。而结构化日志以 键值对 方式记录,天然适配 JSON、可直接被日志平台(如 ELK、Loki)解析。

logger.Info("User logged in",
    zap.String("username", "john_doe"),
    zap.Int("user_id", 12345),
    zap.String("ip", "192.168.1.1"),
)
  • 支持 JSON、Console 等编码器;
  • 与 ELK 等日志平台无缝对接;
  • 支持嵌套字段、错误结构序列化等丰富特性。

3 日志级别

等级说明适用场景线上推荐
Debug最详细的日志,通常用于调试打印变量、中间状态、依赖调用、分支判断等❌ 关闭或采样
Info常规操作日志启动成功、连接建立、业务操作完成等✅ 开启
Warn可恢复异常、潜在问题重试、超时、配置不合理、降级、非致命失败等✅ 开启
Error业务或系统出错写库失败、请求处理异常、panic 恢复等✅ 开启
Fatal打日志后强制退出程序初始化失败、致命异常✅ 少量使用

4 Logger Vs SugaredLogger

特性LoggerSugaredLogger
类型安全✅ 强类型字段❌ 接受 interface{}
性能🚀 极致优化⚡ 稍逊一筹
格式化支持❌ 不支持 printf 格式✅ 支持 Infow / Infof 等
推荐使用场景核心高频路径,性能敏感开发阶段、调试日志、便捷场景
sugar := logger.Sugar()     // Logger → SugaredLogger
logger := sugar.Desugar()   // SugaredLogger → Logger

5 核心组件

5.1 zapcore.Core - 日志处理核心

core := zapcore.NewCore(encoder, writeSyncer, level)
logger := zap.New(core)

掌控日志的编码、输出、等级,所有自定义行为的入口。

5.2 zapcore.Encoder- 日志格式编码器

zapcore.NewJSONEncoder(cfg)      // 结构化日志推荐
zapcore.NewConsoleEncoder(cfg)   // 控制台开发推荐

支持字段定制、时间格式、颜色高亮等。

5.3 zapcore.WriteSyncer - 输出目标

确定日志写到哪里去,文件还是 stdout 还是同时,多个 Core 合并输出,就能实现 " 控制台和文件同时打印 “。

core := zapcore.NewTee(
    zapcore.NewCore(jsonEnc, zapcore.AddSync(os.Stdout), zap.DebugLevel),
    zapcore.NewCore(jsonEnc, zapcore.AddSync(file), zap.InfoLevel),
)

5.4 zapcore.LevelEnabler - 日志级别控制器

支持简单基础配置和动态热切换日志等级:

level := zapcore.DebugLevel   // 基础配置
level := zap.NewAtomicLevel()
level.SetLevel(zap.WarnLevel) // 运行时动态修改

结合 HTTP 接口,可实现日志级别在线调整。

5.5 zap.Field - 结构化日志字段

Zap 的日志核心字段,如:

zap.String("user", "alice")
zap.Int("age", 30)
zap.Error(err)
zap.Any("data", value)      // 自动推断,性能略低

6 常用配置

  • zap.WithCaller(true/false):是否记录调用者信息(文件名和行号)。
  • zap.AddCallerSkip(int):调整调用者信息的跳过层级(用于封装日志库时)。
  • zap.AddStacktrace(level):在指定级别及以上自动记录堆栈信息(通常是 ErrorLevel 或 WarnLevel)。
  • zap.Fields(fields…):为 Logger 添加全局字段(对该 Logger 的所有日志都生效)。
  • zap.ErrorOutput(writeSyncer):指定内部错误(如写入失败)的输出位置。

7 实践与集成

7.1 日志采样

防止高频重复日志刷屏、占用资源,适用于高并发场景,降低非核心日志的 I/O 压力。

core := zapcore.NewSampler(
    baseCore,           // 原始 core
    time.Second,        // 采样窗口
    100,                // 首 100 条不过滤
    100,                // 之后每 100 条采样 1 条
)

7.2 与 context.Context 集成

通过 context 传递 traceId、userId 等 " 链路信息 " 到日志中。

type ctxKey struct{}

func WithLogger(ctx context.Context, logger *zap.Logger) context.Context {
    return context.WithValue(ctx, ctxKey{}, logger)
}

func FromContext(ctx context.Context) *zap.Logger {
    if logger, ok := ctx.Value(ctxKey{}).(*zap.Logger); ok {
        return logger
    }
    return zap.L() // fallback
}

方便在 Gin/gRPC 中注入请求级日志上下文。

7.3 分布式链路追踪

  • 与 tracing 系统(如 OpenTelemetry)结合;
  • 日志中记录 traceId/spanId,便于定位全链路调用。

7.4 日志切割与归档

Zap 本身不处理日志切割,推荐使用 lumberjack,支持压缩、保留策略、文件归档等。

hook := &lumberjack.Logger{
    Filename: "/var/log/app.log",
    MaxSize:  100,   // MB
    MaxAge:   7,     // days
    Compress: true,
}
writeSyncer := zapcore.AddSync(hook)