zerolog需显式配置才能正常工作:必须在main开头调用setgloballevel设级别,手动设置levelfieldname和timefieldformat并用with().timestamp()注入时间戳,日志只应输出到stdout/stderr,静态字段须初始化时注入。

zerolog 不是“配置完就能用”的日志库,它默认不输出 level 字段、不带时间戳、不校验字段类型,直接照抄示例大概率日志查不到、级别被静默丢弃、线上 panic 无声无息。
为什么 log.Info().Msg("ok") 在线上没输出?
zerolog 默认全局级别是 zerolog.TraceLevel,但它的“全局级别”只控制是否调用写入逻辑——而你如果没显式设置 zerolog.SetGlobalLevel(),又恰好在容器里运行(比如 Kubernetes),stdout/stderr 被重定向或缓冲,就可能根本看不到日志;更常见的是:你写了 log.Warn() 却没设级别,结果 warn 被静默吞掉。
- 必须在
main()开头第一行调用zerolog.SetGlobalLevel(zerolog.InfoLevel)(或更低) - 级别常量是
zerolog.DebugLevel/zerolog.InfoLevel/zerolog.ErrorLevel,不是字符串 - 已有 logger 实例不会响应
SetGlobalLevel()的后续变更,重启进程才生效 - 开发时可加
zerolog.ConsoleWriter{Out: os.Stderr}看彩色文本,但上线务必关掉——JSON 才能被 fluent-bit 正确解析
怎么让每条日志都带 level 和 time 字段?
zerolog 默认 JSON 输出里没有 level 字段,log.Warn() 只是构造器标记,不自动注入字段;同理,时间戳也得手动加,否则 Loki 查日志连排序都困难。
- 在创建 logger 前设置:
zerolog.LevelFieldName = "level",再配LevelFieldMarshalFunc转小写 - 时间戳必须用
.With().Timestamp().Logger()注入,且zerolog.TimeFieldFormat = time.RFC3339要提前设好 - 别在
.Msg()里拼"time: " + time.Now().String()——破坏结构化,字段变字符串,Loki 没法当 label 提取 - 字段名严格区分大小写和类型:
Str("user_id", id)和Int("user_id", id)写错会 panic 或丢数据
错误日志为什么堆栈不展开、Err(err) 没反应?
zerolog 的 .Err(err) 不是打印 err.Error(),而是尝试递归展开 error 链(包括 fmt.Errorf("wrap: %w", err) 中的 cause)。但如果 err == nil,它就什么也不写;如果用了 Str("err", err.Error()),堆栈就彻底丢失。
- 永远用
.Err(err),不要用.Str("err", err.Error()) - 确保 error 实现了
Unwrap() error或嵌套了%w,否则深层原因无法透出 - 开发时可临时加
zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack(需引入github.com/pkg/errors)补全 stack 字段 - 线上环境禁用
AddCaller()和AddStackTrace()——性能损耗大,且 fluent-bit 通常已在采集层加了 source 字段
能不能写文件?K8s 里该往哪输出?
能写,但**在容器里写文件等于主动脱离日志平台管控**。Kubernetes 只抓 os.Stdout 和 os.Stderr,写 /var/log/app.log 后,日志既不会进 Loki,也无法按 pod_name 过滤,更没法做轮转压缩。
- 生产环境唯一合规输出目标是
os.Stdout或os.Stderr(按 level 分流) - 想实现 error 日志单独落盘?不行——fluent-bit 支持按 JSON 字段
level == "error"过滤并路由,这才是正解 - 本地调试要文件?用
lumberjack.Logger,但仅限 dev;路径必须绝对,权限设0644,且不能和容器挂载冲突 - 别在代码里写
os.OpenFile("/var/log/...", os.O_CREATE|os.O_APPEND)——没锁、没轮转、没 close 控制,磁盘爆掉前你都收不到告警
最易被忽略的一点:所有静态字段(如 service、env、host)必须在初始化 logger 时用 .With().Str("service", "order-api").Logger() 注入,而不是每次 log.Info().Str("service", ...) 重复写——前者是零分配,后者每次调用都新建 map,高并发下 GC 压力陡增。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











