惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog
B
Blog RSS Feed
小众软件
小众软件
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 聂微东
WordPress大学
WordPress大学
月光博客
月光博客
S
SegmentFault 最新的问题
Engineering at Meta
Engineering at Meta
量子位
V
Visual Studio Blog
罗磊的独立博客
Last Week in AI
Last Week in AI
The Cloudflare Blog
H
Help Net Security
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Azure Blog
Microsoft Azure Blog
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 Django DRF 性能优化 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming 缓存使用的一些经验 Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 Go: gin validation 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结
Go: 一种error wrap调用链处理方式
2021-01-26 · via Posts on WKLKEN THINKING

这是在做 Golang 项目中的一些实践

背景

在做一套内部公共服务, 提供后台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时包含关键的调用参数/返回值等等, 但是不能包含敏感数据