跳到主要内容

日志管理

问题​

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

相关链接​