fiber.logger()中间件仅负责将预格式化字符串写入io.writer,不控制日志格式;定制字段顺序、添加trace_id等需手写中间件,用fmt.sprintf或zerolog构造结构化日志,并用lumberjack安全轮转文件。

fiber.Logger() 中间件不控制格式,只负责写入
Fiber 的 fiber.Logger() 中间件本身不解析或重排日志内容,它只是把预格式化的字符串(比如 "GET /api/users 200 12ms")原样写到你传入的 io.Writer。想改字段顺序、删时间戳、加 trace_id,不能靠配置开关,必须自己构造那条字符串。
常见错误是以为调用 fiber.Logger(fiber.Config{...}) 就能定制格式——其实 Config 里根本没有 format 字段;它只接受一个 io.Writer 和几个样式开关(如 EnableColor),不影响日志结构本身。
- 真正可控的只有:是否带颜色、是否输出状态码、是否显示请求体长度
- 所有字段拼接逻辑都在中间件内部硬编码,不可插拔
- 若需 JSON 或带 level 字段的日志,必须绕过
fiber.Logger(),手写中间件
手写中间件时用 fmt.Sprintf 拼接最直接
在自定义中间件里用 fmt.Sprintf 控制每个字段的有无、顺序和格式,是最轻量且确定性最强的方式。例如要加毫秒级耗时、去掉文件行号、强制小写 status:
func LoggerPrint() fiber.Handler {
return func(c *fiber.Ctx) error {
start := time.Now()
err := c.Next()
status := c.Response().StatusCode()
duration := time.Since(start).Milliseconds()
logLine := fmt.Sprintf("[%s] %s %s %d %.2fms\n",
start.Format("2006-01-02 15:04:05"),
strings.ToUpper(c.Method()),
c.Path(),
status,
duration,
)
fmt.Print(logLine) // 或写入 lumberjack.File
return err
}
}
- 注意:
c.Response().StatusCode()必须在c.Next()之后取,否则为 0 - 别用
time.Now().UnixMilli(),它和请求开始时间不一致;必须用start变量 - 路径中含 query 参数?用
c.OriginalURL()替代c.Path(),但要注意 XSS 风险
想输出 JSON 日志?别碰标准 log 包
Go 标准 log 包输出的是纯字符串,无法拆出 level、msg、ts 等原始字段。强行用正则从 log.Printf 的输出里提取,既不可靠又损耗性能。
正确做法是跳过 log,直接用 zerolog 或 zap 构造结构化日志对象:
import "github.com/rs/zerolog"
<p>var logger = zerolog.New(os.Stdout).With().Timestamp().Logger()</p><p>func JSONLogger() fiber.Handler {
return func(c <em>fiber.Ctx) error {
start := time.Now()
err := c.Next()
logger.Info().
Str("method", c.Method()).
Str("path", c.Path()).
Int("status", c.Response().StatusCode()).
Float64("duration_ms", time.Since(start).Seconds()</em>1000).
Err(err).
Send()
return err
}
}</p>
-
zerolog默认输出紧凑 JSON,不换行;加.Pretty()可美化(仅调试用) - 不要在中间件里调
logger.Error().Msg("xxx")后再返回err—— 这会导致同一条错误被记两次 - 若需兼容老代码的
log.Printf调用,可封装一个CustomLogger.Printf(),内部转成logger.Info().Msgf()
并发写文件时必须用 lumberjack,别手写判断
有人在中间件里每次打开文件、判断大小、重命名、压缩……结果高并发下出现 file is locked 或日志丢失。根本原因是没处理原子写和锁竞争。
lumberjack.Logger 内部已用 sync.Mutex 保证写入安全,并通过 os.Rename 原子切换文件,这才是生产可用的做法:
w := &lumberjack.Logger{
Filename: "logs/app.log",
MaxSize: 10 * 1024 * 1024, // 10MB,单位是字节,不是 "10MB"
MaxBackups: 7,
MaxAge: 28, // 天
Compress: true,
}
app.Use(func(c *fiber.Ctx) error {
// ... 拼 logLine
_, _ = w.Write([]byte(logLine))
return c.Next()
})
-
MaxSize必须是整数,写成10 * 1024 * 1024,不是"10MB"或10_000_000(后者易读错) -
lumberjack不支持按天切割;如需 daily rotate,得配合系统logrotate或换rotatelogs - 别在
defer w.Close()—— 它是长生命周期对象,Close 会中断所有后续写入
关键点其实就一个:Fiber 不管日志长什么样,只管把它塞进你给的 Writer。格式、结构、分发策略,全得你自己定。最容易被忽略的是——你以为在改中间件,其实是在重写日志流水线。











