fiber默认日志中间件不能直接换zap,因其输出纯文本、无结构、不兼容zap字段模型,硬套用会导致字段丢失、时间戳重复、无法注入trace_id;高频调用logger.with()还会引发内存分配压力。

为什么 Fiber 默认日志中间件不能直接换 Zap
Fiber 的 fiber.Logger 中间件输出的是纯文本、无结构、不可扩展的字符串,和 Zap 的结构化字段模型完全不兼容。硬套用会导致字段丢失、时间戳重复、无法注入 trace_id 等上下文——不是“加了 Zap”,而是“Zap 白加”。更糟的是,若在中间件里每次调用 logger.With() 创建新实例,高频请求下会触发大量内存分配,反而拖慢服务。
必须用 fiber.New() 注入自定义 Logger 实例,而非中间件
Fiber 不像 Gin 那样提供 c.Set() 或 c.Context().Value() 的通用上下文传递机制;它的 ctx.Locals 是临时存储,不适合传 *zap.Logger(易被覆盖、无类型安全)。正确做法是把 *zap.Logger 作为字段注入到 Fiber 应用启动时的 struct 中,再通过 handler 函数闭包或自定义 context key(需强类型封装)透出:
- 初始化 Fiber app 前先构建全局
*zap.Logger,用zap.NewProduction(),禁用TimeKey自定义格式(避免time.Now().Format()分配) - 不要在
app.Use()里做ctx.Locals["logger"] = logger.With(...)—— 这等于每个请求都 new 一个 encoder 实例 - 推荐写法:定义
type App struct { fiber *fiber.App; logger *zap.Logger },所有 handler 接收*App指针,复用同一 logger 实例
zap.NewProduction() 是唯一可上生产的配置起点
别碰 zap.NewDevelopment():它默认启用颜色、行号、堆栈展开、缩进 JSON,导致日志体积膨胀 3 倍以上,ELK 解析失败率飙升,CPU 占用翻倍。哪怕只在某个 debug handler 里调一次 logger.Debug(),整个 logger 实例就退化为低性能路径。生产环境必须从 zap.NewProduction() 出发,按需微调:
- 关闭采样:
config := zap.NewProductionConfig(); config.Sampling = nil(高频 trace 日志会被静默丢弃) - 锁定 writer:
zapcore.Lock(zapcore.AddSync(file)),裸写文件句柄在多 goroutine 下 panic - 禁用时间格式化:
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder,别用zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
HTTP 请求日志字段要预置,不是每条都 With()
字段合并(logger.With())有开销,尤其含字符串拼接或 interface{} 转换时。高频请求下,logger.With(zap.String("path", c.Path()), zap.String("method", c.Method())) 每秒几百次,GC 压力明显。应该:
- 提前在全局 logger 上挂通用字段:
logger = logger.With(zap.String("service", "auth-api"), zap.String("env", "prod")) - 只在 handler 内叠加请求级字段:
app.logger.Info("request completed", zap.Int("status", status), zap.Duration("latency", dur)) - trace_id 必须从 header 提取并预置:
if id := c.Get("X-Request-ID"); id != "" { logger = logger.With(zap.String("req_id", id)) },别等日志打点时再查
最易被忽略的一点:Fiber 没有内置 panic 恢复日志钩子,logger.Sync() 必须在 os.Interrupt 和 syscall.SIGTERM 信号处理中显式调用,否则进程退出时缓冲区日志会截断——这不是 Zap 的 bug,是 Fiber 启动模型决定的。











