golang之errors包
errors包常用方法
func Unwrap(err error) error // 获得err包含下一层错误 func Is(err, target error) bool // 判断err是否包含target func As(err error, target interface{}) bool // 判断err是否为target类型
自定义错误信息
errors.New("这是自定义错误")
# 使用fmt进行错误包装
fmt.Errorf("error: %w", err)
errors.Is()
作用:判断被包装过的error是否包含指定错误
var BaseErr = errors.New("base error") func main() { err1 := fmt.Errorf("wrap base: %w", BaseErr) err2 := fmt.Errorf("wrap err1: %w", err1) println(err2 == BaseErr) if !errors.Is(err2, BaseErr) { // err2错误 是否在BaseErr错误树中 panic("err2 is not BaseErr") } println("err2 is BaseErr") } //输出: //false //err2 is BaseErr
errors.As()
作用:判断被包装过的error是否为指定类型
具体说明:提取指定类型的错误,判断包装的 error 链中,某一个 error 的类型是否与 target 相同,并提取第一个符合目标类型的错误的值,将其赋值给 target
type TypicalErr struct { e string } func (t TypicalErr) Error() string { return t.e } func main() { err := TypicalErr{"typical error"} err1 := fmt.Errorf("wrap err: %w", err) err2 := fmt.Errorf("wrap err1: %w", err1) var e TypicalErr if !errors.As(err2, &e) { panic("TypicalErr is not on the chain of err2") } println("TypicalErr is on the chain of err2") println(err == e) } //输出: //TypicalErr is on the chain of err2 //true
errors的最佳实践:
遵循以下建议,我们可以更好地处理 error
:
- 1、一个
error
,应该只被处理一次 - 2、让
error
包含更多的信息 - 3、原始
error
,应保证完整性,不被破坏 - 4、
error
需要被日志记录
为了确保 error
处理的有效性,对于某一层来说,应该保证每个错误只被处理一次,要么打印 error
信息,要么将其传递给上一层,而不是每一层都独立打印 error
信息。
同时,在传递错误给上一层时,应该附带有用的额外信息,并确保不破坏原始错误的完整性,以保证错误的可追溯性。最后,通过记录错误日志可以帮助我们进行问题排查。
我们可以借助第三方库 github.com/pkg/errors
来完成我们的需求。
github.com/pkg/errors
提供了很多实用的函数,例如:
Wrap(err error, message string) error
:该函数基于原始错误err
,返回一个带有堆栈跟踪信息和附加信息message
的新error
Wrapf(err error, format string, args ...interface{}) error
: 和上面的函数功能是一样的,只不过可以对附加信息进行格式化封装WithMessage(err error, message string) error
:该函数基于原始错误err
,返回一个附加信息message
的新error
WithMessagef(err error, format string, args ...interface{}) error
: 和上面的函数功能是一样的,只不过可以对附加信息进行格式化封装Cause(err error) error
:该函数用于提取err
中的原始error
,它会递归地检查error
,直到找到最底层的原始error
,如果存在的话
// controller / middleware res, err := service.GetById(ctx, id) if err != nil { log.Errorf(ctx, "service.GetById failed, original error: %T %v", errors.Cause(err), errors.Cause(err)) log.Errorf(ctx, "stack trace: \n%+v\n", err) ······ } ······ // service article, err := dao.GetById(ctx, id) if err != nil { return errors.WithMessage(err, "dao.GetById failed") } ······ // dao ······ if err != nil { return errors.Wrapf(err, "GetById failed, id=%s, error=%v", id, err) } ······
当在 Dao
层遇到原始错误 Original Error
后,使用 errors.Wrap()
对错误进行封装。这个封装操作可以在保留根因(Origin error
)的同时,提供堆栈信息,并添加额外的上下文信息,然后将封装后的错误传递给上一层处理。
当 service
层接收到 error
之后,使用 errors.WithMessage()
函数,将额外的信息附加到错误上,并继续将错误向上层传递,直至到达 controller 层。在 controller
层,我们可以打印出根因的类型、信息以及堆栈信息,以便更好地进行问题排查。