求教: golang error 和 log 的最佳实践思路

5 天前
 Ayanokouji
用 go 写业务时,遇到 error 时,大多数情况只能一路向上 return err ,我们基于这个场景讨论。

这个场景,和 java 写业务遇到的 checked exception 很类似,写代码时,只能一路向上 throw (或者 catch 住 throw new unchecked exception ),最终由框架统一处理。

如果遇到 go 遇到经验不足的开发者(比如以前的我),就会看到这样的错误日志:

Error: unexpected '>' at the beginning of value

Error: EOF

Error: wrong argument value

嗯。。。这 tm 是啥啊,到底是哪一行代码出错啊。

调用链越长,问题越难排查。

比较通用的 web 业务调用链,一般是 handler -> service -> 中间件(数据库/redis/第三方 api 等)

随着坑踩的多了,现在遇到 err, 一般是 return fmt.Errorf("xxx:%w", err)

日志一般在 handler 层 slog.log("xxx", slog.Any("request", req), slog.Any("err", err))

但是缺少了调用栈,总觉得少了点什么。

请教下各位,如何平衡 error 和 log ,主要是服务于问题排查。

看过 echo 和 huma 的框架 error 处理,都是自定义 err ,框架统一处理

------

ps:那些上来只踩一脚 java 的,还是去多写写代码吧,这种 err ( unexpected '>' at the beginning of value ) 真的比 excetiop (含调用栈) 强吗。
3113 次点击
所在节点    Go 编程语言
81 条回复
pluswu1986
5 天前
业务代码一般不打印日志, 中间件统一在入口处理,底层错误返回 error.Warp 足够的信息(callstack 等) 让中间件统一处理
Ayanokouji
5 天前
@pluswu1986 用的是 github.com/pkg/errors 这个库吗,但是这个已经不更新了
csys
5 天前
没懂你这啥问题

go 记录 error 日志,怎么就没有调用栈了
如果 r 缺少调用栈,那是日志库有问题或者用法有问题
harleyliao
5 天前
一般是在每个出错的地方都打印错误日志, 并返回错误给上一层, 这样通过错误日志就可以推测出调用链了.
Ayanokouji
5 天前
@harleyliao
@csys 日志是能说明是哪一行的。

如果遇到 error 就打印,这样日志太多了吧,比如 sevice 层,查了 5 次 sql ,那就需要写打印五次日志吗。
bli22ard
5 天前
可能是最佳实战的做法是,你调用了标准库,或者第三方库,这些库返回 error 之后,你应该先用一些第三方 error 库 wrap 一下,主要目的是记录一下 error 发生的调用栈,这样上层什么位置拿到 error ,都能打印出来这个 error 是哪个位置发生的。还有一种,目的类似的做法,不记录调用栈,而改为附加一个错误码进去,这样上层的任何调用者也可以知道 error 哪里发生的,不过维护错误码这种方式维护时间越长,越容易搞混乱,导致排查问题困难。
原则上,只要不是本工程的代码生成的 error 都进行一次 wrap ,然后向上 return ,如果没有最上层,则进行错误日志打印。
Ayanokouji
5 天前
@bli22ard 嗯,这种做法比较认同的。第三方 error 库不太好找,有推荐的吗 github.com/pkg/errors 这个已经停更了。
还有为了兼容 slog ,可能还需要封装一些代码。
dylanqqt
5 天前
@Ayanokouji 我们就是遇到 err 就打印,“查五次”就要打印五次的 err ,要写五次 if err != nil ,因为可能第一个 err 会影响第二次的查询,不判断没法往下走啊
Ayanokouji
5 天前
@dylanqqt 不能往下走是没问题的,就是五次 if err != nil 里边还有写五次打印日志,遇到错误也只能是其中一处,实际打印的日志也是一次
csys
5 天前
@Ayanokouji

懂你意思了

> 如果遇到 error 就打印
多数情况下是这样的,我自己是倾向于把所有 error 都打印出来,除非某个地方什么都不做,只是把 error 向上传递,也就是说 1. 产生 error 的地方记录日志 2. 处理 error 的地方记录日志

你遇到的这个问题,看起来更像是需要一个链路追踪 https://opentelemetry.io/docs/languages/go/instrumentation/

至于直接在 error 里记录完整的调用栈也是可以的 https://pkg.go.dev/github.com/cockroachdb/errors
dylanqqt
5 天前
@Ayanokouji 那就是在最底层的那个 err 打印吧
Ayanokouji
5 天前
@dylanqqt 嗯,就是最底层其实不是那么好界定,写法也比较啰嗦。不过这样的日志确实能当调用栈串联起来
bli22ard
5 天前
@Ayanokouji github.com/pkg/errors 这个库还能再战。其实自己定义也可以,实现 error 的 struct ,增加一个 Wrap 函数,参数接受一个 error ,函数体,runtime 拿调用栈,将结果保存在 struct 的成员变量,这样就可以用了
qW7bo2FbzbC0
5 天前
```go
err := doJob()
if err != nil {
return fmt.Errorf("do job failed, %w", err)
}
```
Ayanokouji
5 天前
@qW7bo2FbzbC0 我目前就是这种做法,这种做法的缺陷是,前缀的 message 需要足够清晰和唯一,清晰是为了可读,唯一是为了代码搜索定位当调用栈来用。
还有如果项目做国际化,这种需要定义错误吗,维护比较费精力。
lbp0200
5 天前
这个和语言无关
错误的地方记录日志,包括当前行,当前文件
上级代码只需要知道有错误就行
zacard
5 天前
和 java 的异常处理类似,底层库/函数用 errors 包装携带堆栈,上层统一捕获 error 打印即可。github.com/pkg/errors 虽然不更新了,但是够用了
guanzhangzhang
5 天前
两种,一种是 wrap 一下信息,我博客很多文章搜报错能直接搜到开源项目源码里去,另一种是 zap 这种 logger 能打印带文件 go:行数的
Kauruus
5 天前
你在 error 里带上调用栈也不是不行(例如 https://github.com/go-errors/errors ),然后在顶层处理,打印或者发到 sentry 。
z1829909
5 天前
我一般也是每层返回的时候包一些关键字, 相当于人肉造了一个栈.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1101542

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX