考虑以下代码(https://go.dev/play/p/hDOyP3W_lqW)
package main
import (
"log"
"github.com/pkg/errors"
)
func myError() error {
return errors.New("failing unconditionally")
}
func myError1() error {
return errors.Errorf("annotate with additional debug info: %+v", myError())
}
func myError2() error {
return errors.Errorf("extra debug info: %+v", myError1())
}
func main() {
if err := myError2(); err != nil {
log.Printf("%+v", err)
}
}
我用
errors.New
引发错误,并使用 errors.Errorf
用附加信息对其进行注释。
它做了我想要的事情——记录并打印堆栈跟踪和行号。然而,问题是
log.Printf("%+v", err)
的输出是冗长且重复的:
2009/11/10 23:00:00 extra debug info: annotate with additional debug info: failing unconditionally
main.myError
/tmp/sandbox3329712514/prog.go:10
main.myError1
/tmp/sandbox3329712514/prog.go:14
main.myError2
/tmp/sandbox3329712514/prog.go:18
main.main
/tmp/sandbox3329712514/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1571
main.myError1
/tmp/sandbox3329712514/prog.go:14
main.myError2
/tmp/sandbox3329712514/prog.go:18
main.main
/tmp/sandbox3329712514/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1571
main.myError2
/tmp/sandbox3329712514/prog.go:18
main.main
/tmp/sandbox3329712514/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1571
iiuc,
errors
包每次注释错误时都会向错误附加堆栈跟踪的附加副本,如下面的代码片段所示
// repetitive (thrice) error stack
main.myError
/tmp/sandbox3329712514/prog.go:10
main.myError1
/tmp/sandbox3329712514/prog.go:14
main.myError2
/tmp/sandbox3329712514/prog.go:18
main.main
/tmp/sandbox3329712514/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1571
main.myError1
/tmp/sandbox3329712514/prog.go:14
main.myError2
/tmp/sandbox3329712514/prog.go:18
main.main
/tmp/sandbox3329712514/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1571
main.myError2
/tmp/sandbox3329712514/prog.go:18
main.main
/tmp/sandbox3329712514/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1571
我想要的输出是
// Desired output
2009/11/10 23:00:00 extra debug info: annotate with additional debug info: failing unconditionally
main.myError
/tmp/sandbox3329712514/prog.go:10
main.myError1
/tmp/sandbox3329712514/prog.go:14
main.myError2
/tmp/sandbox3329712514/prog.go:18
main.main
/tmp/sandbox3329712514/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1571
实现此目的的一种方法是仅使用
errors
包来引发错误,然后使用 fmt.Errorf
和 %+v
在调用堆栈中添加其他信息(如下所示 https://go.dev/play /p/OrWe6KUIL_m)。然而,它很容易出错,并且很难强制每个开发人员在大型代码库中使用这种模式。开发人员必须记住使用 errors
包来引发错误,并正确使用 fmt
和 %+v
%s
来打印堆栈跟踪。
我想知道这是否是所需的行为(冗长且重复)。是否可以一致地使用
errors
包来注释调用堆栈中的错误,而不必担心附加重复的堆栈跟踪副本(例如,errors
神奇地知道错误已经有堆栈跟踪)?
有
v
格式说明符 用于打印错误。
%s - 打印错误。如果错误有原因,那么它将是 递归打印。
%v – 它只会打印值。不会打印字段名称。这是使用 Println 时打印结构的默认方式 (打印错误👉🏻如果错误有原因,则会递归打印。)
%+v – 它将打印字段和值。 (扩展格式。将详细打印错误的 StackTrace 的每一帧。)
您的情况:
func myerror() error {
return errors.New("failing unconditionally") // 1️⃣
}
func myerror1() error {
return errors.Errorf("annotate with additional debug info: %+v", myerror()) // 2️⃣
}
func myerror2() error {
return errors.WithStack(myerror1()) // 3️⃣
}
1️⃣ 使用堆栈文件创建新的
error
(errors.New)
2️⃣ 使用“格式化”消息创建新的
error
和这个 error
堆栈 (errors.Errorf)
3️⃣ 使用此
error
堆栈创建新的 error
(errors.WithStack)
2022/07/13 11:42:03 annotate with additional debug info: failing unconditionally
github.com/kozmod/idea-tests/core/errors.myerror
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:10 // 1️⃣
github.com/kozmod/idea-tests/core/errors.myerror1
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:14
github.com/kozmod/idea-tests/core/errors.myerror2
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:18
github.com/kozmod/idea-tests/core/errors.TestStack.func1
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:35
testing.tRunner
/Library/GoLang/go1.18.2.darwin-amd64/src/testing/testing.go:1439
runtime.goexit
/Library/GoLang/go1.18.2.darwin-amd64/src/runtime/asm_amd64.s:1571
github.com/kozmod/idea-tests/core/errors.myerror1
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:14 // 2️⃣
github.com/kozmod/idea-tests/core/errors.myerror2
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:18
github.com/kozmod/idea-tests/core/errors.TestStack.func1
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:35
testing.tRunner
/Library/GoLang/go1.18.2.darwin-amd64/src/testing/testing.go:1439
runtime.goexit
/Library/GoLang/go1.18.2.darwin-amd64/src/runtime/asm_amd64.s:1571
github.com/kozmod/idea-tests/core/errors.myerror2
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:18 // 3️⃣
github.com/kozmod/idea-tests/core/errors.TestStack.func1
/Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:35
testing.tRunner
/Library/GoLang/go1.18.2.darwin-amd64/src/testing/testing.go:1439
runtime.goexit
/Library/GoLang/go1.18.2.darwin-amd64/src/runtime/asm_amd64.s:1571
1️⃣ 第一个
error
堆栈开始
2️⃣ 第二个
error
堆栈开始
3️⃣ 第三个
error
堆栈开始
您可以简单地使用堆栈创建“root”错误,然后添加消息(换行)“root”错误
func myerror3() error {
return errors.New("failing unconditionally")
}
func myerror4() error {
return errors.WithMessage(myerror3(), "annotate with additional debug info")
}
func myerror5() error {
return errors.WithMessage(myerror4(), "myerror5")
}
func main() {
if err := myerror5(); err != nil {
log.Printf("%+v", err)
}
}
或
func myError() error {
// create error (github.com/pkg/errors + fmt) with stack (message)
return fmt.Errorf("%+v", errors.New("failing unconditionally"))
}
func myError1() error {
return fmt.Errorf("annotate with additional debug info: %v", myError())
}
func myError2() error {
return fmt.Errorf("extra debug info: %v", myError1())
}
func main() {
if err := myError2(); err != nil {
log.Printf("%v", err)
}
}
我不太喜欢 github.com/pkg/errors,所以我编写了自己的具有类似行为的包,我可以在其中控制堆栈的格式。
您可以在 github.com/go-msvc/errors 中找到我的包并根据需要复制或更改它。
它没有errors.New(),只有errors.Error(),它具有相同的功能。您可以在副本中更改它以与标准副本保持一致(我也会的)。
我还用errors.Wrapf(err, "...xxx failed...") 包装错误。如果您只是像您一样附加到字符串内,则格式化程序无法遍历图层。
所以,当我拿走你的 main.go 并更改时:
2022/07/14 13:35:33 main.go(18):额外的调试信息,因为 main.go(14):用额外的调试信息进行注释,因为 main.go(10):无条件失败
这对我有用,可以避免堆栈中出现大量混乱。
如果您想要换行符,您也可以添加它,但我更喜欢日志文件中的错误消息没有换行符。
代码:
package main
import (
"log"
"github.com/go-msvc/errors"
)
func myError() error {
return errors.Error("failing unconditionally")
}
func myError1() error {
return errors.Wrapf(myError(), "annotate with additional debug info")
}
func myError2() error {
return errors.Wrapf(myError1(),"extra debug info")
}
func main() {
if err := myError2(); err != nil {
log.Printf("%+v", err)
}
}
github.com/pkg/errors
的精神继承者,解决了github.com/pkg/errors
的一长串问题。这是相关的两个:
errors.Wrap
)。%w
中使用 errors.Errorf
格式动词,它用标准 Go 包装来包装原始错误。您可以使用它作为
github.com/pkg/errors
的直接替代品。因此,上面的示例将通过更改导入并使用 %w
:来正常工作
package main
import (
"log"
"gitlab.com/tozd/go/errors"
)
func myError() error {
return errors.New("failing unconditionally")
}
func myError1() error {
return errors.Errorf("annotate with additional debug info: %w", myError())
}
func myError2() error {
return errors.Errorf("extra debug info: %w", myError1())
}
func main() {
if err := myError2(); err != nil {
log.Printf("%+v", err)
}
}
输出:
2009/11/10 23:00:00 extra debug info: annotate with additional debug info: failing unconditionally
main.myError
/tmp/sandbox3373322165/prog.go:10
main.myError1
/tmp/sandbox3373322165/prog.go:14
main.myError2
/tmp/sandbox3373322165/prog.go:18
main.main
/tmp/sandbox3373322165/prog.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:267
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1650