zerolog 默认静默低于tracelevel的日志,需在首次log调用前设全局级别或新建logger时指定level;自定义logger不响应setgloballevel变更;err()需错误实现unwrap()/stacktrace()且启用errorstackmarshaler;容器中只写os.stdout/stderr;level字段需提前设置levelfieldname和levelfieldmarshalfunc。

zerolog 不是“教程型日志库”,它是个零分配、结构化优先的生产级工具——用错方式,轻则日志全丢,重则服务卡死。
为什么 log.Info().Msg() 什么都没输出?
这是最常踩的坑:zerolog 默认静默所有低于 TraceLevel 的日志,且不报错、不提示。log.Info() 调用成功了,但被全局级别拦在门外。
- 必须显式设置最低生效级别:
zerolog.SetGlobalLevel(zerolog.InfoLevel)(注意:要在任何log.Xxx()调用前执行) - 如果用了
log.Logger = zerolog.New(...).With().Logger()自定义 logger,它**不响应**SetGlobalLevel的后续变更——得自己在新建时传zerolog.New(w).Level(zerolog.InfoLevel) - 开发时容易误开
ConsoleWriter,上线却忘了切回os.Stdout;而ConsoleWriter输出带颜色/缩进,会被 fluent-bit 当作非法 JSON 丢弃
如何让 Err(err) 真正展开堆栈?
只写 .Str("err", err.Error()) 是自废武功——丢失嵌套错误、没有文件行号、Grafana 里点不开堆栈。但 .Err(err) 要起效,得满足两个硬条件:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- error 类型必须实现
Unwrap()或StackTrace()(如github.com/pkg/errors、go.opentelemetry.io/otel/codes包裹过的错误) - zerolog 必须启用 stack 字段:在初始化 logger 前加
zerolog.ErrorStackMarshaler = func(err error) interface{} { return stack.Trace().TrimRuntime() }(需引入github.com/rs/zerolog/pkg/errors) - 别在
.Msg()里拼接错误文本,例如.Msg("failed: " + err.Error())——这会让err字段变成空对象,结构化崩解
容器环境必须避开哪些路径和写法?
Kubernetes 日志采集链路只认 os.Stdout 和 os.Stderr。一旦写本地文件,等于主动退出可观测体系。
- 禁止使用
os.OpenFile("/var/log/app.log", os.O_CREATE|...):容器内目录可能只读、不存在、重启即失,且无轮转、无锁、无 close 控制 - 禁止硬编码
service_name字段到业务代码里;应统一在入口初始化:log.Logger = zerolog.New(os.Stdout).With().Str("service_name", os.Getenv("SERVICE_NAME")).Str("env", os.Getenv("ENV")).Timestamp().Logger() - 字段名必须是顶层 key,不能嵌套:Loki 只索引 label,
.Fields(map[string]interface{}{"req": req})会把整个req当字符串塞进 logfmt,查不到user_id
zerolog.LevelFieldName 设了为啥日志里还是没 level?
因为默认不输出 level 字段——zerolog 把它当“元信息”处理,不像 zap 那样默认开启。光设 LevelFieldName 不够,还得配 LevelFieldMarshalFunc 并确保 logger 实例真正用了它。
- 必须在创建 logger **之前** 设置:
zerolog.LevelFieldName = "level";zerolog.LevelFieldMarshalFunc = func(l zerolog.Level) string { return strings.ToLower(l.String()) } - 如果用了
log.Output(...)替换底层 writer,它会覆盖掉已配置的 level 字段逻辑,得用zerolog.New(w).With().Level(zerolog.InfoLevel).Logger()显式重建 - 验证方法:启动后直接看一条
log.Warn().Msg("test")输出,JSON 里有没有"level":"warn"——没有就说明配置时机或方式错了
关键点其实就一个:zerolog 的配置是“一次性快照”,不是运行时动态代理。所有 SetXXX 调用都只影响之后新建的 logger 实例,对已存在的 log.Logger 或子 logger 完全无效。这点和 zap 的 ReplaceGlobals 或 slog 的 SetDefault 逻辑完全不同,容易想当然踩空。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










