不能直接用zap.newproduction()替换gin默认日志,因其写stderr不落盘、启用采样丢日志、字段名小驼峰与平台下划线习惯不匹配、缺writesyncer锁致并发panic。

为什么不能直接用 zap.NewProduction() 替换 Gin 默认日志
它默认写 os.Stderr,不落盘;默认启用采样(sampling),高频请求日志会被静默丢弃;字段名是小驼峰(如 httpStatus),但 Kibana/Loki 等平台习惯下划线(http_status),导致字段无法被索引。更关键的是:它没配 WriteSyncer 锁,多 goroutine 并发写文件会 panic。
lumberjack.Logger 的 MaxAge 不是“按天归档”
MaxAge: 7 指的是单个日志文件从创建起最多存活 7 天,不是“每天生成一个新文件”。如果某天没写日志,就不会生成当天的文件,MaxAge 就不会触发清理。容易误以为“设了 7 天就自动按天切”,结果发现日志全堆在 app.log 里不动。
-
Filename必须是绝对路径,否则 daemon 模式下可能写到/根目录 -
MaxSize单位是 MB,建议设100而非1024:太大拖慢读取,太小引发频繁 IO 切割 -
LocalTime: true必须显式开启,否则文件名用 UTC 时间,中国服务器上归档时间错乱
Gin 中间件里别反复调用 logger.With()
每次 logger.With(zap.String("path", r.URL.Path)) 都会拷贝字段、重建 encoder 实例,开销比日志本身还高。高频接口(如健康检查)下,这会吃掉可观 CPU。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 通用字段(
service_name、env、host)应在全局 logger 初始化时就With()好 - 请求级字段(
req_id、path、method)用中间件注入 context 或结构体 receiver,避免重复构造 - 绝对不要在 middleware 里调
logger.Sync()—— 它阻塞整个请求链,应只在进程退出前调一次
字段命名不一致会导致 Kibana 查不到日志
Zap 默认字段名是 caller、httpStatus、reqId,但多数日志平台(Loki、阿里 SLS、Datadog)默认按 caller、http_status、req_id 建索引。字段名对不上 → 字段不被解析 → status:500 搜不到任何结果。
必须在 zapcore.EncoderConfig 中显式映射:
encoderConfig := zapcore.EncoderConfig{
LevelKey: "level",
TimeKey: "ts",
CallerKey: "caller",
MessageKey: "msg",
NameKey: "logger",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
// 关键:手动覆盖字段名
encoderConfig.EncodeLevel = func(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString("level", l.String())
}
// 但更稳妥做法是直接改 Key 名(如把 httpStatus 改成 http_status),而非重写 encode 函数
真正麻烦的不是配置本身,而是团队里有人改了字段名却没同步更新 Logstash/Kibana 的 pipeline,线上查问题时才发现字段根本不存在。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










