默认的fiber.logger()不能用于生产环境,因其仅输出到stdout、无轮转、无时间戳、不区分日志级别、不支持结构化输出,无法满足线上快速定位请求id、ip、耗时及错误的需求。

为什么默认的 fiber.Logger() 不能直接用在生产环境
它默认把日志写到 os.Stdout,没做轮转、没加时间戳格式控制、不区分 level、也不支持结构化输出。线上出问题时,你根本没法快速定位是哪个请求、哪个 IP、耗时多少、是否报错。
实际项目里得自己封装一层中间件,接管日志输出目标和内容结构。
- 必须用
fiber.New()创建 app 时禁用默认 logger:Logger: false - 手动注册自定义中间件,别用
app.Use(fiber.Logger()) - 日志 writer 建议用
lumberjack.Logger做文件切割,避免单文件无限增长
怎么让每条日志带上请求 ID 和响应耗时
Fiber 的上下文 c 本身不带 trace ID,得靠中间件生成并注入。响应耗时也不能靠 time.Since() 粗略算——中间件执行顺序会影响精度,必须用 c.Locals 存起始时间。
func LoggerMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
start := time.Now()
id := uuid.New().String()
c.Locals("req_id", id)
c.Set("X-Request-ID", id)
// 记录请求
log.Printf("[REQ] %s %s %s %s", id, c.Method(), c.OriginalURL(), c.IP())
err := c.Next()
// 记录响应
status := c.Response().StatusCode()
latency := time.Since(start)
log.Printf("[RES] %s %d %v %s", id, status, latency, c.UserContext().Err())
return err
}
}
-
c.Locals是线程安全的,比全局 map 或 context.WithValue 更适合存请求级数据 - 别在
c.Next()前调用c.Status()或c.Send(),否则c.Response().StatusCode()可能拿不到真实状态码 - 如果用了 recover 中间件,
c.UserContext().Err()可能为 nil,建议额外判断err != nil
如何对接 zap 或 zerolog 实现结构化日志
Fiber 日志中间件只负责“触发记录”,不负责“怎么记”。真正打日志的动作要交给结构化日志库,比如 zerolog 输出 JSON,方便 ELK 或 Loki 收集。
关键点在于:把 *fiber.Ctx 里的字段(method、path、status、latency、ip、user-agent)全塞进 log event,而不是拼字符串。
import "github.com/rs/zerolog"
func ZerologMiddleware(logger *zerolog.Logger) fiber.Handler {
return func(c *fiber.Ctx) error {
start := time.Now()
id := c.GetRespHeader("X-Request-ID")
err := c.Next()
latency := time.Since(start)
event := logger.Info().
Str("req_id", id).
Str("method", c.Method()).
Str("path", c.Path()).
Int("status", c.Response().StatusCode()).
Str("ip", c.IP()).
Dur("latency", latency).
Str("user_agent", c.Get("User-Agent"))
if err != nil {
event.Err(err)
}
event.Send()
return err
}
}
- 传入的
*zerolog.Logger必须已配置好 output(如写文件或 stdout),中间件不负责初始化 - 不要在中间件里调用
logger.With()创建新 logger——每次请求都 new 会丢掉全局 level 和 hooks - 如果启用了
fiber.Compress(),c.Response().Body()已被压缩,别试图读它来记 response body
为什么日志中间件一定要放在 recover 中间件之后
顺序错了就看不到 panic 导致的错误详情。Fiber 的 fiber.Recover() 会捕获 panic 并设置 c.Response().StatusCode() 为 500,但不会恢复 panic 后的执行流——也就是说,recover 中间件之后的其他中间件(包括日志)仍会被调用,但 c.Next() 已返回,你拿不到原始 error。
- 正确顺序:
app.Use(fiber.Recover())→app.Use(LoggerMiddleware()) - 如果把日志放 recover 前,panic 发生时
c.Next()永远不会返回,日志那行永远不执行 - 哪怕用了
defer,也拦不住 panic 跳过后续语句——只有 recover 能真正“接住”它
最常被忽略的是:本地开发时 panic 很少触发,日志看着正常;一上生产,接口偶发 panic,日志里却只有半条记录,排查时完全没线索。











