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 层,我们可以打印出根因的类型、信息以及堆栈信息,以便更好地进行问题排查。


 

 

 

posted @ 2023-06-15 19:12  X-Wolf  阅读(446)  评论(0)    收藏  举报