Go: 一种error wrap调用链处理方式
这是在做 Golang 项目中的一些实践
背景
在做一套内部公共服务, 提供后台API调用的时候, 某些情况下, 会返回500错误, 此时有两种做法
- 在后台记录详细的500日志, 返回给调用方一个
request_id
, 调用方拿request_id查询日志定位问题 - 直接在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时包含关键的调用参数/返回值等等, 但是不能包含敏感数据