1) 背景
不相熟代码的状况下, 拿到一个谬误排查, 无奈串起来整个代码的调用链路, 谬误产生堆栈, 排查艰难、迟缓, 依据trace只能看见服务调用链
2) 现状
1) 反复解决
// 反复: 逐层返回谬误, 各层记录日志func foo() error{ return errors.New("foo")} func bar() error{ err := foo() if err != nil { log.Error(err) return err // handle error once, 应该记日志或者返回 } return nil} func handle() error { err := bar() if err != nil { log.Error(err) return err // handle error once, 应该记日志或者返回 } return nil}
2) 没有堆栈
// 没有谬误链(堆栈): 当谬误达到利用下层, 不好跟踪本源func foo() error { return errors.New("foo") } func test() error{ err := foo() if err != nil { log.Error(err) return err } return nil} func main(){ err := test() if err := nil { fmt.Println("%+v",err) // foo, 没有从main->test->foo的堆栈 }}
3) 如何处理错误
1) 指标
- 谬误被日志记录, 不重复记录
- 下层利用处理错误, 珍重100%的完整性
- 能够增加上下文信息
2) 办法
"github.com/pkg/errors"
errors.Wrapf()
: Wrapf returns an error annotating err with a stack trace (带有堆栈, 反复会有屡次堆栈)errors.WithMessagef()
: WithMessagef annotates err with the format specifier (不带堆栈, 可携带信息)
3) 最佳实际
- 最底层(比方公共库)返回原始谬误, 无需wrap
- 利用上层流转谬误, 应用wrap带上堆栈
- 利用内办法相互调用, 防止反复wrap导致反复堆栈, withMessage携带信息即可
4) 成果
在打印或者记录谬误, 个别由日志中间件在最上层实现, 应用占位符 %+v
, 比方
// log error with stackfmt.Printf("err:%+v", err)
4) Q&A
1) ecode为自定义错误信息构造体, 是否会扭转前端提醒?
前端可能正确展现对应的提醒是基于返回的ecode中的code和message, 前端展现message信息。
- wrap之前: 利用返回原始error(即为根因error), 如果该error为ecode, 领有对应的code和message, 则前端能失常展现, 否则则不能
- wrap之后: 应用层通用返回原始error(即为根因error), wrap加上堆栈, 出口处框架会主动cause寻找根因, 之后和wrap之前逻辑统一
也就是说, 重点在于返回的原始error是否为ecode才是重点, 与是否wrap没有关系
2) 每一层error向上抛时如何携带上下文信息?
pkg/errors 包提供了以下办法
- errors.Wrap(),
- errors.Wrapf(),
- errors.WithMessage(),
- errors.WithMessagef()
应用注意事项
Wrap
类会携带堆栈, 利用上层调用一次即可, 防止屡次调用造成堆栈屡次打印WithMessage
类不会携带堆栈, 只会包装一层- 带
f
类占位符的函数, 能够携带一些参数等上下文信息
5) references
- go-exceptions
- why-go-error-handling-is-awesome
- stack-traces-and-the-errors