go fiber 错误处理需四步:1. 先注册 fiber.recover() 拦 panic;2. 用 wrapper 中间件统一处理业务 error;3. 参数校验前置防隐式 panic;4. errorhandler 仅设状态码/日志,不重复写响应。

Go Fiber 框架不会自动捕获 panic,也不会统一处理业务 error;你必须显式配置 recover 机制 + 单独设计 error 流程,否则服务会崩溃或返回空响应。
fiber.Recover() 必须放在所有路由注册之前
fiber.Recover() 是唯一能拦截 panic 的中间件,但它只对它之后注册的 handler 生效。一旦 panic 发生在它前面(比如某个中间件里提前 panic),进程直接退出。
- 错误写法:
app.Get("/api", handler)写在app.Use(fiber.Recover())前 → panic 不被捕获 - 正确顺序:先
app.Use(fiber.Recover()),再注册所有路由和中间件 - 开发期可加
fiber.Config{EnablePrintRoutes: true},启动时看打印的中间件顺序是否符合预期
Config.ErrorHandler 里别重复写响应体
Config.ErrorHandler 函数签名是 func(*fiber.Ctx, error) error,它本身就在响应阶段。如果 handler 已调过 c.JSON() 或 c.SendString(),再在 ErrorHandler 里调一次,就会触发 http: superfluous response.WriteHeader 错误。
- 安全做法:先检查
c.Response().StatusCode() == 0,再决定是否写响应 - 更稳妥方案:ErrorHandler 只设状态码、打日志、上报监控;响应体交给一个预定义函数统一生成,避免分散写
- 别在里面调
c.Next()或试图“继续执行”——它不是中间件链,而是兜底出口
业务 error 不能靠 Recover 拦,得用 wrapper 中间件
fiber.Recover() 只抓 panic,对 return errors.New("not found") 这类业务 error 完全无效。这类 error 必须由你主动 return,并在顶层用 wrapper 拦住。
- 典型 wrapper 写法:
func(c fiber.Ctx) error { err := realHandler(c); if err != nil { return c.Status(500).SendString(err.Error()) }; return nil } - 所有路由 handler 都要返回
error,否则 wrapper 拦不住 - panic 和业务 error 的日志级别、告警策略应不同:panic 记 ERROR + full stack,业务 error 记 WARN + 精简上下文
ctx.Params() / ctx.Query() 类型转换容易引发 panic
Fiber 不做运行时类型校验,c.Params("id") 返回 string,你直接传给 strconv.Atoi 而不检查空值或格式,就会 panic —— 这类 panic 很隐蔽,但恰恰是 fiber.Recover() 要兜底的重点。
- 推荐写法:
idStr := c.Params("id"); if idStr == "" { return c.Status(400).SendString("id required") },再转整型 - 更健壮方案:用
go-playground/validator/v10做结构体绑定校验,把参数解析和校验收口到一处 - 别依赖
ctx.Body()做大 payload 解析——底层 fasthttp 复用 buffer,标准库惯用法会触发额外内存分配,性能下降明显
真正稳定的错误处理,不靠一层 recover 包打天下;panic 捕获、业务 error 统一返回、参数校验前置、日志与监控分层,这四件事缺一不可。最容易被忽略的是:ErrorHandler 里重复写响应,以及把业务 error 当成 panic 去 recover。这两点线上出问题时几乎必现。











