go 中无法全局捕获普通 error,仅 panic 可通过 defer+recover 捕获;错误需显式返回与检查,全局管理指统一构造、分类、传递和处理,如 http 中间件封装 *apperror 并记录完整堆栈。

Go 程序中无法全局捕获 panic 以外的错误
Go 没有类似 Python 的 try...except 或 Java 的全局异常处理器,error 值必须显式返回和检查。所谓“全局错误管理”,本质是统一错误构造、分类、传递与最终处理(如日志、响应封装),而非自动拦截未处理的 error。
常见误操作是试图用 recover 捕获普通 error——这完全无效,recover 只对 panic 生效。
- 所有业务逻辑中的
err != nil判断不能省略,这是 Go 的契约 - 真正能“全局”干预的只有
panic(通过defer + recover)和程序退出前的日志钩子(如log.Fatal替换) - HTTP 服务中可借助中间件统一处理 handler 返回的
error,但这仍属“显式传播后的集中处理”,非运行时拦截
用自定义 error 类型实现上下文透传与分类
标准 errors.New 或 fmt.Errorf 生成的 error 缺乏结构化信息。推荐用实现了 error 接口的结构体携带状态码、追踪 ID、原始错误等字段。
示例:
立即学习“go语言免费学习笔记(深入)”;
type AppError struct {
Code int
Message string
TraceID string
Err error // 原始 error,用于链式调用
}
func (e *AppError) Error() string {
return e.Message
}
func (e *AppError) Unwrap() error {
return e.Err
}
- 避免直接用
fmt.Errorf("xxx: %w", err)包裹而不保留业务语义;应优先用自定义类型封装 - HTTP handler 中可统一检查
err是否为*AppError,据此设置 HTTP 状态码和响应体 - 日志记录时,应同时输出
e.Code、e.TraceID和e.Err的完整堆栈(用fmt.Printf("%+v", err))
HTTP 服务中用中间件统一错误响应与日志
在 Gin / Echo / net/http 中,可通过中间件捕获 handler 执行过程中的 panic 和显式返回的 error,并标准化输出格式。
- Gin 示例:注册
gin.Recovery()处理 panic,再加一层自定义中间件处理return err场景 - 不要在每个 handler 里重复写
if err != nil { c.JSON(500, ...) };提取为公共函数如sendError(c, err) - 日志需包含请求 ID(从 header 或中间件生成)、路径、方法、耗时、错误类型(
*AppError还是底层库 error)、简明 message - 敏感错误信息(如数据库连接失败详情)不应直接返回给前端,但必须完整记入服务端日志
日志库选型与 error 字段提取的关键细节
使用 zap 或 zerolog 时,直接传 err 变量往往只打印 Error() 方法结果,丢失堆栈和嵌套信息。
- zap:用
zap.Error(err)可自动展开fmt.Formatter实现(需 error 实现fmt.Formatter);否则手动调用fmt.Sprintf("%+v", err)并作为字符串字段写入 - zerolog:无原生 error 支持,必须用
.Str("error", fmt.Sprintf("%+v", err)),否则只记录err.Error() - 务必开启日志采样或分级:高频非关键错误(如用户参数校验失败)打 debug 日志,系统级错误(DB 连接中断)打 error 并告警
最易被忽略的是:自定义 error 类型若未实现 Unwrap(),会导致 errors.Is() 和 errors.As() 失效,进而让错误分类和重试逻辑出错。










