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

huangapple go评论107阅读模式
英文:

How can I wrap a golang error into an opaque error?

问题

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

errors.Wrap()创建一个带有堆栈跟踪的新错误,但不是我定义的不透明类型。如何同时实现这两个功能(添加堆栈跟踪并将其作为MyErr类型,且temporarytrue)?

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 {   // 模拟另一个包中的函数,产生一个错误
    return fmt.Errorf("f1 error")
}

func f2() error {
    err := f1()
    myErr := errors.Wrap(err, "f2 error")   // Wrap() 添加堆栈跟踪
    // 如何将其封装为临时的 MyErr?
    return myErr
}

func f3() error {
    err := f2()
    return fmt.Errorf("f3 error: %+v", err) // 这里不要使用 Wrap(),否则会得到另一个堆栈跟踪
}

func f4() error {
    err := f3()
    return fmt.Errorf("f4 error: %+v", err) // 这里不需要使用 '+ ',但不会有害
}

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

这是正确的,但我希望首先打印出"temporary error"。

假设f1实际上是第三方或内置代码,返回标准的error类型。f2是我代码中接收该错误的第一个函数,并且需要在适当的情况下将其设置为临时错误。(如果它最初是临时错误,那将是一个后续问题,但我认为我可以解决。)

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

英文:

How do I wrap an error into an opaque error (as described by Dave Cheney in https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully)? Also, I want the opaque error to have a stacktrace, and for that to be retained though the return chain.

errors.Wrap() creates a new error with the stacktrace, but not of my opaque type. How do I do both (add the stack trace and make it a MyErr with temporary as 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)
    }
}

This prints the following:

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

That's correct except I want to see "temporary error" printed first.

Assume f1 is actually in 3rd party or built-in code, returning a standard error type. f2 is the first function in my code receiving that error, and needs to make it a Temporary when appropriate. (If it's a Temporary originally, that would be a follow-on question but I think I can figure it out.)

I want the pattern for handling errors returned from our code to be consistent throughout the project, which will be relatively large.

答案1

得分: 1

你可以使用github.com/pkg/errors函数来实现这个功能。这是因为用于包装的错误类型是未导出的,所以你无法将其嵌入到自定义错误中。

然而,考虑到你不反对使用除了标准库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 { // 模拟另一个包中的函数,产生一个错误
	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())
		}
	}
}
英文:

You can't really do this with the github.com/pkg/errors function. This is because the error type used for wrapping is unexported, so you can't embed it into your own custom error.

However seeing as you are not opposed to using an error library other than the stdlib errors package, here is how you could do it with the juju errors package(because it's Err type is exported):

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

huangapple
  • 本文由 发表于 2021年12月17日 10:08:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/70387666.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定