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

问题描述 投票:0回答:3

考虑以下代码(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
神奇地知道错误已经有堆栈跟踪)?

go error-handling stack-trace
3个回答
2
投票

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)
    }
}

游乐场


0
投票

我不太喜欢 github.com/pkg/errors,所以我编写了自己的具有类似行为的包,我可以在其中控制堆栈的格式。

您可以在 github.com/go-msvc/errors 中找到我的包并根据需要复制或更改它。

它没有errors.New(),只有errors.Error(),它具有相同的功能。您可以在副本中更改它以与标准副本保持一致(我也会的)。

我还用errors.Wrapf(err, "...xxx failed...") 包装错误。如果您只是像您一样附加到字符串内,则格式化程序无法遍历图层。

所以,当我拿走你的 main.go 并更改时:

  • New() 到 Error(),
  • Errorf("....: %+v", err) 到 Wrapf(err, "..."),并且
  • 导入 github.com/go-msvc/errors, 然后它输出:

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)
    }
}

0
投票

我不久前制作了

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
© www.soinside.com 2019 - 2024. All rights reserved.