一、怎样的错误处理和编码是合适的
error对于go是必选题,因为这就是go对异常错误的处理方式。既然是必选题,那么怎样处理才是优秀的设计呢?
1.1 error缺乏堆栈信息怎么办
以下代码代码的问题,在于当main中调用test发生错误的时,我们根本无法知道内部哪个方法发生了问题。当问题是作为使用者main,需要知道test里面的哪行代码出了问题吗?
支持方:社区
其他语言说,这是需要的:因为我们都能做到,我们可以直接在 v,err:=test()调用时抛出错误信息和堆栈信息。 虽然Go语言对错误的设计非常简洁,但是对于我们开发者来说,很明显是不足的,比如我们需要知道出错的更多信息,在什么文件的,哪一行代码?只有这样我们才更容易的定位问题。
还有比如,我们想对返回的error附加更多的信息后再返回,比如以上的例子,我们怎么做呢?我们只能先通过Error方法,取出原来的错误信息,然后自己再拼接,再使用errors.New函数生成新错误返回。
如果我们以前做过java开发,我们知道Java的异常是可以嵌套的,也就是说,通过这个,我们很容易知道错误的根本原因,因为Java的异常,是一层层的嵌套返回的,不管中间经历了多少包装,我们可以通过cause找到根本错误的原因。
反对方:某歌
test不是告诉你发生了某个错误了吗?你确定还需要某一行?error不已经告诉你不能正常调用了吗?我们力求简洁,别再烦我。
func m1() (int, error) {
time.Sleep(1 * time.Microsecond)
return 1, errors.New("error1")
}
func m2() (int, error) {
time.Sleep(2 * time.Microsecond)
return 2, errors.New("error2")
}
func test() (string, error) {
v1, err1 := m1()
if err1 != nil {
return "", err1
}
v2, err2 := m2()
if err2 != nil {
return "", err2
}
return strconv.Itoa(v1 + v2), nil
}
func main(){
v,err:=test()
}
社区
既然你不给我们,我们自己来一个吧 github.com/pkg/errors 这个库本质能做什么呢?
1.包装error(且带有堆栈信息),且能找到原始的error
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
e := errors.WithStack(err)
2.和golang的标准库很贴近,提供UnWrap,New等方法,但注意google故意不提供Wrap方法,仅仅提供fmt.Errof %w的方法,恶心到了吧。且目前这个库缺少Join方法,暂时这个库没人维护了。但其实调整还是容易的。
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
3.官方的fmt.Errorf("%w",err)生成的error没有堆栈信息 用pkg包的error我们最后可以通过如下得到堆栈信息,因为堆栈被设计进去了。
%s,%v //功能一样,输出错误信息,不包含堆栈
%q //输出的错误信息带引号,不包含堆栈
%+v //输出错误信息和堆栈
1.2 zap ,logrus对error堆栈信息的处理
package main
import (
"github.com/sirupsen/logrus"
)
func main() {
logrus.SetReportCaller(true) //开启调用堆栈信息,但是我们只能记录到记日志的位置。
logrus.Info("info msg")
}
$ go run main.go time="2020-12-07T21:46:03 08:00" level=info msg="info msg" func=main.main file="D:/code/golang/src/github.com/darjun/go-daily-lib/logrus/caller/main.go:10"
package main
import (
"encoding/json"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
rawJSON := []byte(`{
"level": "debug",
"encoding": "json",
"outputPaths": ["stdout", "/tmp/logs"],
"errorOutputPaths": ["stderr"],
"initialFields": {"foo": "bar"},
"encoderConfig": {
"messageKey": "message",
"levelKey": "level",
"levelEncoder": "lowercase",
"callerKey": "caller",//主要这里
}
}`)
var cfg zap.Config
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
panic(err)
}
cfg.EncoderConfig.EncodeCaller = zapcore.FullCallerEncoder
logger, err := cfg.Build()
if err != nil {
panic(err)
}
defer logger.Sync()
logger.Info("logger construction succeeded")
}