如何将 golang 错误包装成不透明错误?

Posted

技术标签:

【中文标题】如何将 golang 错误包装成不透明错误?【英文标题】:How can I wrap a golang error into an opaque error? 【发布时间】:2022-01-20 01:18:24 【问题描述】:

如何将错误包装成不透明的错误(如 Dave Cheney 在https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 中所述)?另外,我希望不透明错误有一个堆栈跟踪,并通过返回链保留。

errors.Wrap() 使用堆栈跟踪创建一个新错误,但不是我的不透明类型。我该怎么做(添加堆栈跟踪并使其成为MyErr,临时为true)?

package main

import (
    "fmt"
    "github.com/pkg/errors"
)

type temporary interface 
    Temporary() bool


func IsTemporary(err error) bool 
    te, ok := err.(temporary)
    return ok && te.Temporary()


type MyError struct 
    error
    isTemporary bool


func (e MyError) Temporary() bool 
    return e.isTemporary


func f1() error    // imitate a function from another package, that produces an error
    return fmt.Errorf("f1 error")


func f2() error 
    err := f1()
    myErr := errors.Wrap(err, "f2 error")   // Wrap() adds the stacktrace
    // how to wrap it as a temporary MyErr?
    return myErr


func f3() error 
    err := f2()
    return fmt.Errorf("f3 error: %+v", err) // don't Wrap() here or we get another stacktrace


func f4() error 
    err := f3()
    return fmt.Errorf("f4 error: %+v", err) // the '+' isn't needed here but does no harm


func main() 
    err := f4()
    if err != nil 
        if IsTemporary(err) 
            fmt.Println("temporary error")
        
        fmt.Printf("oops: %+v\n", err)
    

这将打印以下内容:

oops: f4 error: f3 error: f1 error
f2 error
main.f2
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:32
main.f3
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:38
main.f4
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:43
main.main
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:48
runtime.main
        /usr/local/go/src/runtime/proc.go:255
runtime.goexit
        /usr/local/go/src/runtime/asm_amd64.s:1581

这是正确的,只是我想先看到“临时错误”。

假设f1 实际上在第三方或内置代码中,返回标准的error 类型。 f2 是我的代码中收到该错误的第一个函数,需要在适当的时候将其设为临时函数。 (如果最初是临时的,那将是一个后续问题,但我想我可以弄清楚。)

我希望处理从我们的代码返回的错误的模式在整个项目中保持一致,这将是相对较大的。

【问题讨论】:

f4 返回的错误不是Temporary。使用fmt.Errorf%w,然后您可以使用errors.Unwrap 来测试是否有任何错误是Temporary 我的问题是如何使它成为f2 中的临时对象。此外,在f3 中使用fmt.Errorf%w 会删除回溯。代码应该如何知道它有多少层? f3f4(以及 f5 等)的模式需要相同。 【参考方案1】:

github.com/pkg/errors 函数无法真正做到这一点。这是因为用于包装的错误类型未导出,因此您无法将其嵌入到您自己的自定义错误中。

但是,鉴于您不反对使用 stdlib errors 包以外的错误库,以下是使用 juju errors 包的方法(因为它的 Err 类型已导出):

package main

import (
    "fmt"

    "github.com/juju/errors"
)

type temporary interface 
    Temporary() bool


func IsTemporary(err error) bool 
    for 
        te, ok := err.(temporary)
        if ok 
            return te.Temporary()
        

        er, ok := err.(*errors.Err)
        if ok 
            err = er.Underlying()
            continue
        

        return false
    


type MyError struct 
    errors.Err
    isTemporary bool


func (e MyError) Temporary() bool 
    return e.isTemporary


func f1() error  // imitate a function from another package, that produces an error
    return errors.Errorf("f1 error")


func f2() error 
    err := f1()
    wrappedErr := errors.Annotate(err, "f2 error")
    return &MyError
        Err:         *wrappedErr.(*errors.Err),
        isTemporary: true,
    


func f3() error 
    err := f2()
    return errors.Annotate(err, "f3 error")


func f4() error 
    err := f3()
    return errors.Annotate(err, "f4 error")


func main() 
    err := f4()
    if err != nil 
        if IsTemporary(err) 
            fmt.Println("temporary error")
        
        if e, ok := err.(*errors.Err); ok 
            fmt.Printf("oops: %+v\n", e.StackTrace())
        
    

【讨论】:

以上是关于如何将 golang 错误包装成不透明错误?的主要内容,如果未能解决你的问题,请参考以下文章

golang os.Create 导致“没有这样的文件或目录”错误

角度动画因不透明度​​设置而失败(以及更多错误)

如何将每个回调包装在一个地方以改进错误处理

golang - 如何获得错误“已评估但未使用”

如何避免在错误响应中将错误集合包装在 Apollo Server V2 中的错误对象中

Golang 实现守护主进程