fiber默认不集成zap因设计哲学是“不封装、不强制”,需手动注册中间件并注入req_id等字段;裸调logger.info会丢失trace_id,查问题如盲人摸象。

直接在 Fiber 中用 zap.L() 或裸调用 logger.Info() 不行——Fiber 没内置日志中间件,也不自动注入上下文字段,不手动桥接就只能打 stdout,丢了 trace_id、req_id、method 等关键上下文,查问题时等于盲人摸象。
为什么 Fiber 默认不带 Zap 集成
Fiber 是轻量级 HTTP 框架,设计哲学是“不封装、不强制”,所有中间件需显式注册。它不像 Gin 有 ginzap、Echo 有 echolog 那样的官方 zap 插件,所以你不能指望 app.Use(zap.Middleware) 这种写法能跑通。
-
zap.NewProduction()返回的是*zap.Logger,不是 Fiber 中间件类型(fiber.Handler) - Fiber 的
c.Context()是结构体值,不是 Go 原生context.Context,无法直接用context.WithValue注入 logger - 常见错误:把
*zap.Logger存进c.Locals后,在 handler 里反复调用logger.With(),导致每请求新建 encoder 和 core,压测时 CPU 直接拉满
正确注册中间件并注入请求级字段
必须自己包装一个 fiber.Handler,把 logger 实例 + 请求元信息一起塞进 c.Locals,且只做一次 With(),避免字段合并开销。
- 用
c.Locals("logger", logger.With(...)),而不是c.Locals("logger", logger)后在 handler 里再 With - 必须预置通用字段:
service、env、version;请求级字段只加method、path、req_id(从 header 或 uuid 生成) - 别在中间件里调
logger.Sync()—— 它会阻塞整个请求,Sync 只应在进程退出前调一次 - 示例片段:
func ZapMiddleware(logger *zap.Logger) fiber.Handler { return func(c *fiber.Ctx) error { reqID := c.Get("X-Request-ID") if reqID == "" { reqID = xid.New().String() } l := logger.With( zap.String("req_id", reqID), zap.String("method", c.Method()), zap.String("path", c.Path()), zap.String("service", "user-api"), ) c.Locals("logger", l) return c.Next() } }
在 Handler 中安全取用 logger 并记录错误
Handler 里不能假设 c.Locals("logger") 一定存在,也不能直接断言为 *zap.Logger —— 因为中间件可能没执行或 panic 了。要加类型检查和 fallback。
- 用
if l, ok := c.Locals("logger").(*zap.Logger); ok { ... },否则 fallback 到全局zap.L()(但仅限兜底,不可用于生产) - HTTP 错误不要只打
logger.Error("failed"),必须带 status code 和 error 原因:l.Error("http_error", zap.Int("status", 500), zap.Error(err)) - 别用
Sugar()在 handler 里拼字符串,比如l.Sugar().Infof("user %s login", uid)—— 这会触发反射和内存分配,吞掉性能优势 - panic 恢复要单独写中间件,用
c.Locals("logger")取 logger 记录堆栈,否则 recover 后上下文已丢失
文件输出与轮转必须靠 lumberjack,Zap 自身不切分
zap.NewProduction() 默认只写 os.Stderr,连文件路径都不支持。想落地到 ./logs/app.log 并按大小轮转,必须手搭 lumberjack.Logger 作为 WriteSyncer。
-
Filename必须是绝对路径,相对路径在 systemd daemon 下会写进/根目录,权限失败静默丢日志 -
MaxSize: 100(MB)比1024更稳妥:太大难排查,太小频繁切割 IO -
LocalTime: true必须设,否则文件名用 UTC 时间,在中国服务器上归档时间错乱 - 示例 writer 构造:
writeSyncer := zapcore.AddSync(&lumberjack.Logger{ Filename: "/var/log/myapp/app.log", MaxSize: 100, MaxBackups: 30, MaxAge: 7, Compress: true, LocalTime: true, })
最易被忽略的一点:Zap 的 EncoderConfig 中 EncodeLevel 若没设成 zapcore.CapitalLevelEncoder,Kubernetes 里的 fluent-bit 就认不出 level: error 字段,所有 error 日志进不了告警通道。











