Go: 一种error wrap调用链处理方式

2022年2月5日 283点热度 0人点赞 0条评论

【导读】go API 返回错误有什么好实践?本文做了详细介绍。

背景

在做一套内部公共服务, 提供后台API调用的时候, 某些情况下, 会返回500错误, 此时有两种做法

  1. 在后台记录详细的500日志, 返回给调用方一个request_id, 调用方拿request_id查询日志定位问题
  2. 直接在response body中返回错误信息

选择了问题排查路径短一些的 2, 错误信息中, 需要包含调用链路以及根因错误的详细信息, 提升问题排查的效率

这样, 开发者在看到报错response body的同时, 可以直接知道调用链, 不需要使用请求重新复现问题, 节约了大量的复现/沟通的时间

最终效果

system error[request_id=0838c4090cfa4f4f9dceea5fd8c7b029]: [Handler:validateSystemSuperUser] impls.ListSubjectRoleSystemID subjectType=`user`, subjectID=`test1` fail%!(EXTRA string=test1) => [Cache:GetSubjectRole] SubjectRoleCache.Get subjectType=`user`, subjectID=`test1` fail => [Cache:GetSubjectPK] SubjectPKCache.Get _type=`user`, id=`test1` fail => [SubjectSVC:GetPK] GetPK _type=`user`, id=`test1` fail => [Raw:Error] sql: no rows in result set

处理机制

使用error wrap, 一层层抛出

1. 定义pkg/errorx

注意, package name使用errorx, 可以规避同标准库命名的冲突; 同时import的时候不需要alias

type Error struct {
    message string
    err     error
}

func (e Error) Error() string {
    return e.message
}

func (e Error) Is(target error) bool {
    if target == nil || e.err == nil {
        return e.err == target
    }

    return errors.Is(e.err, target)
}

func (e *Error) Unwrap() error {
    u, ok := e.err.(interface {
        Unwrap() error
    })
    if !ok {
        return e.err
    }

    return u.Unwrap()
}

2. 封装wrapfunc helper

注意,

  • 这里如果是最原始的err(即wrap前的err), 会使用[Raw:Error]标注
  • 如果是中间层的, 会附加layer(哪一层)和function(哪个函数)这两个信息
  • 这里定义了一个 helper 方法, 用于在
func makeMessage(err error, layer, function, msg string) string {
    var message string
    var e Error
    if errors.As(err, &e) {
        message = fmt.Sprintf("[%s:%s] %s => %s", layer, function, msg, err.Error())
    } else {
        message = fmt.Sprintf("[%s:%s] %s => [Raw:Error] %v", layer, function, msg, err.Error())
    }

    return message
}

func Wrapf(err error, layer string, function string, format string, args ...interface{}) error {
    if err == nil {
        return nil
    }

    msg := fmt.Sprintf(format, args...)

    return Error{
        message: makeMessage(err, layer, function, msg),
        err:     err,
    }
}

type WrapfFuncWithLayerFunction func(err error, format string, args ...interface{}) error

func NewLayerFunctionErrorWrapf(layer string, function string) WrapfFuncWithLayerFunction {
    return func(err error, format string, args ...interface{}) error {
        return Wrapf(err, layer, function, format, args...)
    }
}

3. 使用

在某个函数体内, 涉及到需要return err或中间层

errorWrapf := errorx.NewLayerFunctionErrorWrapf("Handler""ListSubject")

_, err := svc.ListPaging(body.Type, body.Limit, body.Offset)
if err != nil {
    err = errorWrapf(err, "svc.ListPaging type=`%s` limit=`%d` offset=`%d` fail!",
            body.Type, body.Limit, body.Offset)
        return err
}

注意, wrap时包含关键的调用参数/返回值等等, 但是不能包含敏感数据

转自:

wklken.me/posts/2021/01/26/golang-error-wrap.html

 - EOF -

推荐阅读(点击标题可打开)

1、golang 中 bufio 包的实现原理

2、Go 网络轮询器 epoll Listen 实现

3、Go Middleware指南及其原理

Go 开发大全

参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。

图片

关注后获取

回复 Go 获取6万star的Go资源库

分享、点赞和在看

支持我们分享更多好文章,谢谢!

8060Go: 一种error wrap调用链处理方式

root

这个人很懒,什么都没留下

文章评论