zerolog必须在main()首行调用setgloballevel设级别、提前配置timefieldformat和levelfieldname,并用with().timestamp()注入时间戳,否则日志缺失关键字段导致loki无法索引。

别在 main() 末尾才调用 zerolog.SetGlobalLevel(),也别直接用 log.Info().Msg() 就开跑——90% 的线上日志静默、字段缺失、Loki 查不到,都栽在这两步上。
初始化 logger 前必须设三件事
zerolog 不是开箱即用的库,它默认不写 time、不带 level、不校验字段类型。漏掉任意一项,日志就变成“合法 JSON 字符串”,但关键字段全丢,下游系统(Loki / Grafana)根本没法索引或告警。
-
zerolog.TimeFieldFormat = time.RFC3339:必须在创建 logger 前设置,否则时间戳是纳秒整数,不是可读字符串 -
zerolog.SetGlobalLevel(zerolog.InfoLevel):必须放在import之后、任何log.Xxx()之前,通常就是func main()第一行 -
zerolog.New(os.Stdout).With().Timestamp().Str("service_name", os.Getenv("SERVICE_NAME")).Str("env", os.Getenv("ENV")).Logger():这是最简安全起点,.With()是注入静态字段的唯一可靠方式,.Timestamp()单独调用无效
HTTP 中间件里动态注入 trace_id 和请求字段
静态字段(service、env)只设一次,但每个请求要带 trace_id、method、path、status、duration 才能串联链路。不能靠全局 logger 拼接,得用 .With() 派生新实例。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 从
X-Trace-IDHeader 提取,或用uuid.NewString()生成,存入context.Context - 中间件里构造子 logger:
logger := log.Ctx(r.Context()).With().Str("trace_id", tid).Str("method", r.Method).Str("path", r.URL.Path).Logger() - 记录响应后,用
logger.Info().Int("status", w.Status()).Dur("duration", time.Since(start)).Msg("http request"),别拼Msg("status=200 duration=12ms") - 禁止在 handler 里重复写
.Str("service_name", "xxx"),字段名必须是顶层 key,嵌套在.Fields(map[string]interface{})里会被 Loki 当纯文本丢弃
Err(err) 要生效,得满足两个硬条件
只写 .Str("err", err.Error()) 等于放弃可观测性——堆栈无法展开、错误链断裂、Grafana 里点不开源码行。但 .Err(err) 不是调了就灵,它依赖底层 error 实现和 logger 配置。
- error 类型必须实现
Unwrap()或StackTrace(),比如github.com/pkg/errors.Wrap()包裹过的错误 - 必须提前启用 stack 序列化:
zerolog.ErrorStackMarshaler = func(err error) interface{} { return stack.Trace().TrimRuntime() }(需引入github.com/rs/zerolog/pkg/errors) -
.Err(nil)安全,会输出"error":{},不是 panic;但.Msg("failed: " + err.Error())会让error字段变空对象,结构化崩解 - panic 捕获时,必须用
log.Panic().Stack().Err(r).Msg("unhandled panic"),而不是log.Printf("panic: %v\n%v", r, debug.Stack())
容器环境里只写 os.Stdout,且禁用颜色和缩进
Kubernetes 日志采集链路只认 os.Stdout 和 os.Stderr。一旦写文件、用 ConsoleWriter、或输出带 ANSI 码的彩色日志,fluent-bit 就会当非法 JSON 丢弃整行。
- 绝对禁止
os.OpenFile("/var/log/app.log", ...):目录可能只读、重启即失、无轮转、无并发控制 - 禁用颜色:
zerolog.SetGlobalLevel(zerolog.InfoLevel)已隐含关闭 color,但若用了ConsoleWriter必须切回os.Stdout - 确保每条日志是单行 JSON:zerolog 默认满足,但若中间混入
\n或手动拼接多行字符串,采集器会切碎日志 - 别在
log.Info().Msg()里塞大结构体(如完整 HTTP body),字段值过大可能触发 fluent-bit 截断或内存溢出
最难绷的其实是字段命名一致性——trace_id 在 A 服务叫 traceId,B 服务叫 x-trace-id,Loki 标签就对不上;service_name 和 service 混用,告警规则就得写两套。统一 schema 不是选配,是日志能查的前提。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










