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
特性 | Logger | SugaredLogger |
---|---|---|
类型安全 | ✅ 强类型字段 | ❌ 接受 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)