英文:
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
类型,且temporary
为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 { // 模拟另一个包中的函数,产生一个错误
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())
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论