Go错误处理规范化
参考
kuriball大佬公司内的文档
处理方式
目前在进行一个业务的错误处理优化,方便后续快速定位问题。
错误处理不当主要体现在以下方面:
- 没有打印堆栈信息,定位问题困难
- 重复携带堆栈信息,冗余
- 一个error打印多次(例如每一层都打印)
首先看几个和 日志 / error 相关的库
- errors标准库
- github.com/pkg/errors (下文直接称为pkg-errors)
- 公司内部 ecode(error code)
- 公司内部 log
堆栈信息是排查问题的一个重要手段,例如:
1 | package main |
需要注意格式化输出error时,%v只会输出错误文本,stack
根据堆栈信息我们可以快速定位到日志位置和调用链路
不携带堆栈信息方法:
- errors标准库 - errors.New
- pkg-errors - errors.WithMessage
- 公司内部的 ecode 创建error
携带堆栈信息的方法:
- pkg-errors 的 errors.New
- pkg-errors / errors - errors.Wrap
- pkg-errors - errors.WithStack
直接看源码即可,注释也很详细
公司内部在web框架中加入了中间件,会统一打印日志,推荐先把中间件的代码过一下,会更清楚整个流程
错误打印原则:
- 最底层(比如公共库)返回原始错误, 无需wrap
- 应用下层流转错误, 使用wrap带上堆栈
- 应用内方法互相调用, 避免重复wrap导致重复堆栈, WithMessage携带信息即可
公共库返回的error是否携带堆栈信息?
可以看下源码,例如json.Marshal
1 | func Marshal(v any) ([]byte, error) { |
先不去处理错误,发生panic后recover,从里面提取原因并转换为jsonError,一般发生panic后会程序崩溃会输出堆栈信息,但recover中只能获得报错信息,想要获取堆栈信息可以通过debug.Stack(),例如下面的代码:
1 | package main |
所以json.Marshal函数是不会输出堆栈信息,如果出现error了需要Wrap或者WithStack一下
wrap 或 WithMessage 后会不会影响到ecode对于前端的提示?
前端能够正确展示对应的提示是基于返回的ecode中的code和message, 前端展示message信息。
- wrap之前: 应用返回原始error(即为根因error), 如果该error为ecode, 拥有对应的code和message, 则前端能正常展示, 否则则不能
- wrap之后: 应用层通用返回原始error(即为根因error), wrap加上堆栈, 出口处框架会自动cause寻找根因, 之后和wrap之前逻辑一致
也就是说, 重点在于返回的原始error是否为ecode才是重点, 与是否wrap没有关系
例如:
1 | // 方式一 |
两种方法都可以,方式一中,先将系统内的错误打印出来方便程序员问题定位,然后通过ecode替换为对应error,为用户展示错误信息。方式二是将qaEcode.QASnapshotAddErr作为原始error,将错误信息加到msg中,最后通过中间件统一打印。
举个例子更加直观一些:
1 | package main |
评论
评论插件加载失败
正在加载评论插件