跳到主要内容

日志管理

问题

Go 项目应该如何记录日志?zap 和 zerolog 的区别是什么?

答案

结构化日志

生产环境用结构化 JSON 日志,而非 fmt.Println

{"level":"info","ts":"2024-01-01T00:00:00Z","msg":"user created","userID":123,"latency":"2ms"}

zap(Uber)

import "go.uber.org/zap"

// 生产环境
logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("user created",
zap.Int("userID", 123),
zap.Duration("latency", time.Millisecond*2),
)

// 带 Sugar 的便捷 API(性能略低)
sugar := logger.Sugar()
sugar.Infow("user created", "userID", 123, "latency", "2ms")
sugar.Infof("user %d created", 123)

zerolog

import "github.com/rs/zerolog/log"

log.Info().
Int("userID", 123).
Dur("latency", time.Millisecond*2).
Msg("user created")

对比

维度zapzerologslog (标准库)
性能极高极高(零分配)中等
API强类型字段链式调用slog.Info("msg", "key", val)
生态最流行轻量Go 1.21 内置

slog(Go 1.21+ 标准库)

import "log/slog"

slog.Info("user created", "userID", 123, "latency", "2ms")

// 自定义 Handler
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
选型建议
  • 新项目:先用 slog(标准库),性能不够再换 zap
  • 高性能:zap 或 zerolog
  • 已有项目:不需要迁移,保持一致即可

日志最佳实践

实践说明
结构化JSON 格式,方便 ELK 采集
分级Debug/Info/Warn/Error
RequestID中间件注入,方便追踪
不记录敏感信息密码、Token 等脱敏
采样高频日志启用采样,避免打爆磁盘

常见面试问题

Q1: 为什么不用 log.Println

答案

  1. 无日志级别(Info/Error 混在一起)
  2. 非结构化(纯文本难以解析)
  3. 无字段支持(无法按 userID 过滤)
  4. 不支持采样和异步写入

Q2: zap 为什么快?

答案

  • 零分配:预分配 buffer,避免 fmt.Sprintf 的内存分配
  • 强类型字段zap.Int("key", val) 不需要反射
  • sync.Pool 复用 encoder

相关链接