【问题标题】:Combine multiple error strings合并多个错误字符串
【发布时间】:2016-02-01 22:11:58
【问题描述】:

我是 golang 新手,我的应用程序需要在一个循环中返回多个错误,稍后需要组合并作为单个错误字符串返回。我无法使用字符串函数来组合错误消息。在返回之前可以使用哪些方法将这些错误组合成一个错误?

package main

import (
   "fmt"
   "strings"
)

func Servreturn() (err error) {

   err1 = fmt.Errorf("Something else occured")
   err2 = fmt.Errorf("Something else occured again")

   // concatenate both the error

   return err3

}

【问题讨论】:

  • 没有足够的信息。客户做什么?你的服务器是做什么的?示例输出。

标签: go error-handling


【解决方案1】:

字符串函数对错误不起作用,因为error实际上是一个实现函数Error()字符串的接口。

您可以在 err1.Error() 和 err2.Error() 上使用字符串函数 但不在“err1”引用本身上。

有些错误是结构,比如从数据库驱动程序中得到的错误。

因此没有自然的方法可以对错误使用字符串函数,因为它们实际上可能不是下面的字符串。

至于结合两个错误:

简单,只需再次使用 fmt.Errorf。

fmt.Errorf("Combined error: %v %v", err1, err2)

或者:

errors.New(err1.Error() + err2.Error())

【讨论】:

  • 除了使用 fmt.Errorf() 以外的任何其他方式,为什么我不能使用字符串函数?
  • 当然。取决于“组合”的真正含义。字符串连接是使用+ 运算符完成的。或者您可以将它们单独存储在一个切片中,append()-ing 值。 这个问题建议你自己完成一个 Go 教程并买一本关于它的书。
  • @GregPetr 可能另一种干净的方法是拥有自己的 CombinedError 结构,它将错误列表作为字段并实现 Error() string 方法并做你想做的事,无论是连接字符串或任何东西。
【解决方案2】:

为了扩展@WillC 在评论中提到的内容,可以定义您自己的error 类型,因为error 是一个接口类型。任何实现了Error() string 函数的类型都实现了error 接口。因此,您可以创建一个 CollectionError 来聚合错误并返回一个串联的错误字符串。

type ErrorCollector []error

func (c *ErrorCollector) Collect(e error) { *c = append(*c, e) }

func (c *ErrorCollector) Error() (err string) {
    err = "Collected errors:\n"
    for i, e := range *c {
        err += fmt.Sprintf("\tError %d: %s\n", i, e.Error())
    }

    return err
}

这提供了一个集合函数,将给定的error 附加到一个切片上。在调用 Error() string 时,它会遍历切片并创建一个连接的错误字符串。

func main() {
    collector := new(ErrorCollector)

    for i := 0; i < 10; i++ {
         collector.Collect(errors.New(fmt.Sprintf("%d Error", i)))
    }

    fmt.Println(collector)
}

有一个很棒的golang.org blog post 更详细地检查了错误。 The Go Playground 上提供了该示例的完整示例。

【讨论】:

  • 谢谢,您的示例非常有用。我会试试这个然后回来。
  • @BenCampbell 方法接收器是否有理由成为这里的指针?
  • 感谢您的提示。万一可能会添加一个 nil 错误,Error() 函数应该对收集的错误进行 nil 检查。如果仅通过索引而不是索引+值访问,则需要在索引访问之前先转换错误数组。这是一个示例:play.golang.org/p/h9F5WhcQ5Bs
  • @Rakesh No. func (c *ErrorCollector) Error() (err string) 应该是 func (c ErrorCollector) Error() (err string)
  • 你不需要,但是让一些方法作为指针接收器而一些作为值接收器会违反约定。 Collect 需要是一个指针接收器。
【解决方案3】:

您可以使用strings.Join()append() 函数来实现此切片。

example: golang playgorund

package main

import (
    "fmt"
    "strings"
    "syscall"
)

func main() {

    // create a slice for the errors
    var errstrings []string 

    // first error
    err1 := fmt.Errorf("First error:server error")
    errstrings = append(errstrings, err1.Error())

    // do something 
    err2 := fmt.Errorf("Second error:%s", syscall.ENOPKG.Error())
    errstrings = append(errstrings, err2.Error())

    // do something else
    err3 := fmt.Errorf("Third error:%s", syscall.ENOTCONN.Error())
    errstrings = append(errstrings, err3.Error())

    // combine and print all the error
    fmt.Println(fmt.Errorf(strings.Join(errstrings, "\n")))


}

这将输出一个字符串,您可以将其发送回客户端。

First error:server1 
Second error:Package not installed 
Third error:Socket is not connected

希望这会有所帮助!

【讨论】:

    【解决方案4】:

    人们可能对https://github.com/hashicorp/go-multierror 感兴趣,它将自己描述为“用于将错误列表表示为单个错误的 Go (golang) 包。”。

    【讨论】:

      【解决方案5】:

      使用此功能:

      func JoinErrs(errs ...error) error {
          var joinErrsR func(string, int, ...error) error
          joinErrsR = func(soFar string, count int, errs ...error) error {
              if len(errs) == 0 {
                  if count == 0 {
                      return nil
                  }
                  return fmt.Errorf(soFar)
              }
              current := errs[0]
              next := errs[1:]
              if current == nil {
                  return joinErrsR(soFar, count, next...)
              }
              count++
              if count == 1 {
                  return joinErrsR(fmt.Sprintf("%s", current), count, next...)
              } else if count == 2 {
                  return joinErrsR(fmt.Sprintf("1: %s\n2: %s", soFar, current), count, next...)
              }
              return joinErrsR(fmt.Sprintf("%s\n%d: %s", soFar, count, current), count, next...)
          }
          return joinErrsR("", 0, errs...)
      }
      

      当所有错误都是 nil 时,它会给你 nil,当只有一个非 nil 错误时,它会给你同样的错误,当多个非 nil 错误时,它会给你非 nil 错误的编号列表

      在这里试试:https://play.golang.org/p/WttztCr-xHG

      【讨论】:

        【解决方案6】:

        Go 1.13 更新:

        截至Go version 1.13,语言的errors package 现在直接支持error wrapping

        您可以在fmt.Errorf 中使用%w 动词来包装错误:

        err := errors.New("Original error")
        err = fmt.Errorf("%w; Second error", err)
        

        使用Unwrap 删除最后添加的错误,并返回剩余内容:previousErrors := errors.Unwrap(err)

        Playground Example for errors.Unwrap

        另外两个函数errors.Iserrors.As 提供了检查和检索特定类型错误的方法。

        Playground Example for errors.As and errors.Is


        Dave Cheney 出色的 errors 包 (https://github.com/pkg/errors) 包含一个用于此目的的 Wrap 函数:

        package main
        
        import "fmt"
        import "github.com/pkg/errors"
        
        func main() {
                err := errors.New("error")
                err = errors.Wrap(err, "open failed")
                err = errors.Wrap(err, "read config failed")
        
                fmt.Println(err) // "read config failed: open failed: error"
        }
        

        这还允许附加功能,例如解包错误原因:

        package main
        
        import "fmt"
        import "github.com/pkg/errors"
        
        func main() {
            err := errors.New("original error")
            err = errors.Wrap(err, "now this")
        
            fmt.Println(errors.Cause(err)) // "original error"
        }
        

        以及在指定fmt.Printf("%+v\n", err) 时输出堆栈跟踪的选项。

        您可以在他的博客上找到有关该软件包的更多信息:herehere

        【讨论】:

        • 包装不适用于多个错误。它用于用更多上下文包装一个错误。
        • @dolmen 你能引用这个断言吗?
        • @PauloNeves 到目前为止,已有 17 人批准了我的评论。
        • @dolmen 这是什么意思?
        • @Paulo:@dolmen 断言当在其他语言中像 InnerExceptions 一样使用时,错误包装是最直观的。包装错误不是(兄弟)错误列表,而是(父子)错误链。没有什么可以阻止您像列表一样使用它,但 IMO 它是违反直觉的。
        【解决方案7】:

        Uber 有一个用于此用例的 multierr 包:

        return multierr.Combine(err1, err2)
        

        【讨论】:

          【解决方案8】:

          这似乎对我很有效(空格分隔错误):

          1. 将所有错误放入错误切片/列表/数组中,即:var errors [] error
          2. fmt.Sprintf("%s", 错误)
              var errors []error
              errors = append(errors, fmt.Errorf("error 1"))
              errors = append(errors, fmt.Errorf("error 2"))
              errors = append(errors, fmt.Errorf("and yet another error"))
              s := fmt.Sprintf("%s", errors)
              fmt.Println(s)
          

          【讨论】:

            【解决方案9】:

            有时我需要检测链中是否存在错误的方法。 https://pkg.go.dev/errors 提供的标准方式非常方便:

            someErr:=errors.New("my error")
            fmt.Errorf("can't process request: %w",someErr) 
            ...
            err:=f()
            if errors.Is(err,someErr){...}
            

            但它只能应用于检测到链中最后一个错误的情况。你不能包装 someErr1 然后 someErr2 然后从两个检查中都得到 true:errors.Is(err,someErr1) 和 errors.Is(err,someErr2)

            我用以下类型解决了这个问题:

            func NewJoinedErrors(err1 error, err2 error) JoinedErrors {
                return JoinedErrors{err1: err1, err2: err2}
            }
            
            type JoinedErrors struct {
                err1 error
                err2 error
            }
            
            func (e JoinedErrors) Error() string {
                return fmt.Sprintf("%s: %s", e.err1, e.err2)
            }
            
            func (e JoinedErrors) Unwrap() error {
                return e.err2
            }
            
            func (e JoinedErrors) Is(target error) bool {
                return errors.Is(e.err1, target)
            }
            

            它使用了这样一个事实,即

            如果错误等于目标,则认为错误与目标匹配 或者如果它实现了一个方法 Is(error) bool 这样 Is(target) 返回真。

            所以你可以加入两个错误并在两个检查中都得到肯定的结果:

            someErr1:=errors.New("my error 1")
            someErr2:=errors.New("my error 2")
            err:=NewJoinedErrors(someErr1, someErr2)
            // this will be true because 
            // (e JoinedErrors) Is(target error) 
            // will return true 
            if errors.Is(err, someErr1){...}
            // this will be true because 
            // (e JoinedErrors) Unwrap() error 
            // will return err2 
            if errors.Is(err, someErr2){...}
            

            你可以在这里查看:https://play.golang.org/p/W7NGyfvr0v_N

            【讨论】:

              【解决方案10】:
              func condenseErrors(errs []error) error {
                  switch len(errs) {
                  case 0:
                      return nil
                  case 1:
                      return errs[0]
                  }
                  err := errs[0]
                  for _, e := range errs[1:] {
                      err = errors.Wrap(err, e.Error())
                  }
                  return err
              }
              

              【讨论】:

              • 注意如果第一个错误是 nil 例如 [nil, error1, error2, error3] 你的代码将返回 nil 因为 Wrap 实现 `` func Wrap(err error, message string) error { if err == nil { return nil } err = &withMessage{ cause: err, msg: message, } return &withStack{ err, callers(), } } ``
              • 好点。这假设传递给函数的错误不是零。
              猜你喜欢
              • 2021-02-22
              • 2019-03-13
              • 2013-09-15
              • 1970-01-01
              • 1970-01-01
              • 2015-02-05
              • 1970-01-01
              • 2013-06-07
              相关资源
              最近更新 更多