我想用堆栈跟踪注释错误,因此我使用 /pkg/errors 包。
Go 1.13 添加了 %w - 格式化动词来换行错误。
以下不使用 %w 的程序打印了一个不错的堆栈跟踪:
https://play.golang.org/p/eAwMrwqjCWX
以下仅使用 %w not 稍微修改过的程序:
https://play.golang.org/p/am34kdC0E3o
我该如何将 %w 与错误包装和堆栈跟踪一起使用?
原因是
errors.Errorf
初始化了新的fundamental
错误类型(在pkg/errors
中)。如果您查看它的方法Format(s fmt.State, verb rune)
(当您执行log.Printf
时会触发):
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
所以你看到它只是打印了
io.WriteString(s, f.msg)
。
另外,Errorf
是:
func Errorf(format string, args ...interface{}) error {
return &fundamental{
msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
由于行:
fmt.Sprintf(format, args...)
,您会看到 go vet 的错误(您无法将 %w
传递给 Sprintf
),并且在错误消息的打印中您只看到:
Es gab einen Fehler: %!w(*errors.fundamental=&{failing unconditionally 0xc0000a2000})
而当使用
Wrap
方法时,你会得到 withStack
错误类型,其 Format
方法是:
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
}
}
感谢这一行
fmt.Fprintf(s, "%+v", w.Cause())
,您可以看到一个很好的递归堆栈跟踪。
基本上,你需要像这样使用
%w
:
fmt.Errorf("got error: %w", err)
但是如果您想打印堆栈,这将无济于事。
您也许可以做的是自己实现一个递归打印堆栈跟踪的函数,该函数将在
fmt
或 pkg/errors
的错误链上工作:
type stackTracer interface {
StackTrace() errors.StackTrace
}
type unwrapper interface {
Unwrap() error
}
func printStack(err error) {
if err == nil {
return
}
if ster, ok := err.(stackTracer); ok {
fmt.Printf("%+v", ster)
}
if wrapped, ok := err.(unwrapper); ok {
printStack(wrapped.Unwrap())
}
}
github.com/pkg/errors
已被弃用,并且不会获得新 Go(错误处理)功能的更新。我不久前将它的精神继承者命名为gitlab.com/tozd/go/errors
,它解决了github.com/pkg/errors
的一长串问题。其中之一,
errors.Errorf
仅适用于
%w
。您可以使用
gitlab.com/tozd/go/errors
作为
github.com/pkg/errors
的直接替代品,您的示例程序如下所示:
package main
import (
"log"
"gitlab.com/tozd/go/errors"
)
func myerror() error {
return errors.New("failing unconditionally")
}
func main() {
if err := myerror(); err != nil {
err = errors.Errorf("Es gab einen Fehler: %w", err)
log.Printf("%+v", err)
}
}
输出:
2009/11/10 23:00:00 Es gab einen Fehler: failing unconditionally
main.myerror
/tmp/sandbox3646048541/prog.go:10
main.main
/tmp/sandbox3646048541/prog.go:14
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:267
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1650
请注意,堆栈跟踪正确地从 myerror
(因此发生原始错误的位置)开始,而不是从
main
(您调用
Errorf
的位置)开始。
Errorf
正确检测到原始错误已经有堆栈跟踪,并且不会添加另一个错误或覆盖它。