如何使用 pkg/errors 在 golang 中注释错误并漂亮地打印堆栈跟踪?

Ziz*_* Wu 5 error-handling stack-trace go

考虑以下代码(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)
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用 引发错误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
Run Code Online (Sandbox Code Playgroud)

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

Run Code Online (Sandbox Code Playgroud)

我想要的输出是

// 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
Run Code Online (Sandbox Code Playgroud)

实现此目的的一种方法是仅使用errors包来引发错误,然后使用fmt.Errorfwith%+v在调用堆栈中添加其他信息(例如https://go.dev/play/p/OrWe6KUIL_m)。然而,它很容易出错,并且很难强制每个开发人员在大型代码库中使用这种模式。开发人员必须记住使用errors包来引发错误并fmt正确使用 with%+v %s来打印堆栈跟踪。

我想知道这是否是所需的行为(冗长且重复)。是否可以一致地使用errors包来注释调用堆栈上的错误,而不必担心附加重复的堆栈跟踪副本(例如,神奇errors地知道错误已经有堆栈跟踪)?

koz*_*zmo 2

v 用于打印错误的格式说明符。

\n

%s - 打印错误。如果错误有原因,\n它将被递归打印。

\n

%v \xe2\x80\x93 它将仅打印值。不会打印字段名称。这是使用 Println\n 时打印结构的默认方式(打印错误,如果错误有原因,则会递归打印。)

\n

%+v \xe2\x80\x93 它将打印字段和值。\n(扩展格式。将详细打印错误的 StackTrace 的每一帧。)

\n

在你的情况下:

\n
func myerror() error {\n    return errors.New("failing unconditionally") // 1\xef\xb8\x8f\xe2\x83\xa3\n}\n\nfunc myerror1() error {\n    return errors.Errorf("annotate with additional debug info: %+v", myerror()) // 2\xef\xb8\x8f\xe2\x83\xa3\n}\n\nfunc myerror2() error {\n    return errors.WithStack(myerror1()) // 3\xef\xb8\x8f\xe2\x83\xa3\n}\n
Run Code Online (Sandbox Code Playgroud)\n

1\xef\xb8\x8f\xe2\x83\xa3error使用堆栈文件创建新的(errors.New

\n

2\xef\xb8\x8f\xe2\x83\xa3error使用“格式化”消息和此error堆栈创建新的(errors.Errorf

\n

3\xef\xb8\x8f\xe2\x83\xa3error使用此error堆栈创建新的(errors.WithStack

\n
2022/07/13 11:42:03 annotate with additional debug info: failing unconditionally\ngithub.com/kozmod/idea-tests/core/errors.myerror\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:10 // 1\xef\xb8\x8f\xe2\x83\xa3\ngithub.com/kozmod/idea-tests/core/errors.myerror1\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:14\ngithub.com/kozmod/idea-tests/core/errors.myerror2\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:18\ngithub.com/kozmod/idea-tests/core/errors.TestStack.func1\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:35\ntesting.tRunner\n    /Library/GoLang/go1.18.2.darwin-amd64/src/testing/testing.go:1439\nruntime.goexit\n    /Library/GoLang/go1.18.2.darwin-amd64/src/runtime/asm_amd64.s:1571\ngithub.com/kozmod/idea-tests/core/errors.myerror1\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:14 // 2\xef\xb8\x8f\xe2\x83\xa3\ngithub.com/kozmod/idea-tests/core/errors.myerror2\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:18\ngithub.com/kozmod/idea-tests/core/errors.TestStack.func1\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:35\ntesting.tRunner\n    /Library/GoLang/go1.18.2.darwin-amd64/src/testing/testing.go:1439\nruntime.goexit\n    /Library/GoLang/go1.18.2.darwin-amd64/src/runtime/asm_amd64.s:1571\ngithub.com/kozmod/idea-tests/core/errors.myerror2\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:18 // 3\xef\xb8\x8f\xe2\x83\xa3\ngithub.com/kozmod/idea-tests/core/errors.TestStack.func1\n    /Users/19798572/GolandProjects/idea-tests/core/errors/stack_test.go:35\ntesting.tRunner\n    /Library/GoLang/go1.18.2.darwin-amd64/src/testing/testing.go:1439\nruntime.goexit\n    /Library/GoLang/go1.18.2.darwin-amd64/src/runtime/asm_amd64.s:1571\n
Run Code Online (Sandbox Code Playgroud)\n

1\xef\xb8\x8f\xe2\x83\xa3 第一个的开始error堆栈的开始

\n

2\xef\xb8\x8f\xe2\x83\xa3 第二个的开始error堆栈的开始

\n

3\xef\xb8\x8f\xe2\x83\xa3 第三个开始error堆栈的开始

\n

您可以简单地使用堆栈创建“root”错误,然后添加消息(换行)“root”错误

\n
func myerror3() error {\n    return errors.New("failing unconditionally")\n}\n\nfunc myerror4() error {\n    return errors.WithMessage(myerror3(), "annotate with additional debug info")\n}\n\nfunc myerror5() error {\n    return errors.WithMessage(myerror4(), "myerror5")\n}\n\nfunc main() {\n    if err := myerror5(); err != nil {\n        log.Printf("%+v", err)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

PLAYGROUND

\n

或者

\n
func myError() error {\n    // create error (github.com/pkg/errors + fmt) with stack (message)\n    return fmt.Errorf("%+v", errors.New("failing unconditionally"))\n}\n\nfunc myError1() error {\n    return fmt.Errorf("annotate with additional debug info: %v", myError())\n}\n\nfunc myError2() error {\n    return fmt.Errorf("extra debug info: %v", myError1())\n}\n\nfunc main() {\n    if err := myError2(); err != nil {\n        log.Printf("%v", err)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

PLAYGROUND

\n

  • 谢谢您的回答!`errors.WithMessage` 很高兴知道。遗憾的是,输出并不理想``无条件失败...10 行堆栈跟踪注释有额外的调试信息 myerror5``我用我想要的输出更新了我的问题。我还添加了一个指向更新版本代码的链接,该代码可以满足我的需求。但是,该代码很容易出错,我必须在错误和 fmt 之间切换并正确使用 %+v 。所以我想知道是否可以一致地使用错误包来注释调用堆栈中的错误,而不必担心附加重复副本堆栈跟踪 (2认同)
  • 谢谢@kozmo!我发现 `golang.org/x/xerrors` 比 `github.com/pkg/errors` 更健全,并且可以完全实现我想要的效果(始终使用 `%+v` 而不必担心堆栈跟踪的多个副本):https ://go.dev/play/p/cGeZCOuCJ4s (2认同)