Categorygithub.com/cmstar/go-errx
modulepackage
1.5.0
Repository: https://github.com/cmstar/go-errx.git
Documentation: pkg.go.dev

# README

errx - 更精准的定位和跟踪错误

GoDoc Go codecov License GoVersion Go Report Card

功能:

  • 封装引起错误的错误。
  • 为错误追加方法调用栈信息。
  • 业务预定义异常 BizError
  • 用于处理 recover() 结果的 PreserveRecover 方法。

安装:

go get -u github.com/cmstar/go-errx@latest

调用栈和错误链

Go 程序通常小而精,更多的用于中间件和系统编程,但有时仍会被用在上层的复杂业务里,和 Java 、 .net 同台,此时 Go 的错误处理模式就变得捉襟见肘起来。也许这就不是 Go 的设计意图,但真实的编码场景里,我们免不了碰到这样的情况。

Go 的 error 只是“不太特殊”的值而已(Errors are values),它太过于普通以至于不能像 Java/.net 的 Exception 一样携带足够多的信息。

在复杂的上层业务中,快速定位错误的位置显得极为重要,有时可以用运行性能换工作效率。


Wrap 方法

errx.Wrap 为错误添加更多的细节,返回一个 StackfulError 接口的实现,它包含方法:

  • Cause:记录引起错误的错误,类似 Java 的 Throwable.getCause() 或 .net 的 Exception.InnerException
  • Stack:记录创建错误(即调用 Wrap 方法)时的方法调用栈,类似 Java 的 Exception.printStackTrace() 或 .net 的 Exception.StackTrace
  • Error:即标准库的 error.Error() ,但它包含了 CauseStack 格式化后的信息。输出格式见下文《Describe 方法》。
  • ErrorWithoutStack:只包含错误的描述,不包含 Stack 的信息。

调用栈信息使用标准库的 runtime.CallersFrames 方法获取,有一定的性能开销。

BizError

在业务交互中,我们可能需要根据错误的类别进行不同的处理,原始的 error 等同于一个字符串,难以判断和分类。 errx 包定义了 BizError ,以便对错误进行分类。它是一个特殊的 error ,可通过 errx.NewBizError 方法创建。

BizError 提供:

  • 为每个错误添加一个整数型的错误码 Code ,以便更精准的对错误进行分类和定位,特别是在日志搜索时。通常0表示没有错误,其余值表示有错误,值由具体业务指定。
  • 包含调用栈信息 Stack
  • 包含 Cause ,即引起此错误的错误。

BizError.Error() 返回值格式为: (Code) Message ,不包含 CauseStack

BizError 的使用样例可参考 GoDoc 示例

go-webapi 框架使用 BizError 区分需要返回的业务错误和其他内部错误。

方法

Describe 方法

errx.Describeerrx.Wrap 添加的信息抽取出来,形成一段完整的错误描述,它包含各层级错误的信息及调用栈。

格式为:

最外层错误描述
--- 最外层错误的调用栈信息
=== 第1层内部错误的描述
--- 第1层内部错误的调用栈信息
=== 第2层内部错误的描述
--- 第2层内部错误的调用栈信息
...(逐层展示)
=== 最内层错误的描述
--- 最内层错误的描述的调用栈信息

实际示例可参考 GoDoc 示例


总体而言,就是让 Go 的 error 更像 Java/.net 的 Exception

当一个 errorWrap 之后返回给其调用者,调用者再次使用 Wrap 并返回给更上层的调用者, error 就形成了一个链条。

PreserveRecover 方法

我们可能需要利用应对 panic ,并将相关的错误信息保留下来,代码如下:

func do() (err error) {
    defer func() {
        r := recover()
        if r == nil {
            return
        }

        // 留存错误,丢失了调用栈信息。
        err = fmt.Errorf("do: %v", r)
    }()

    somethingThatMayPanic()
    return nil
}

利用 PreserveRecover 方法,这段代码可以简化成这样,当 recover() 的结果不是 nil 时,它会被 Wrap() 在一个错误里:

func do() (err error) {
    defer func() {
        // 留存错误,并保留调用栈信息。若 recover() 结果为 nil ,则 err 也是 nil 。
        err = errx.PreserveRecover("do", recover())
    }()

    somethingThatMayPanic()
    return nil
}

Run/RunE 方法

用于执行一个可能会 panic 的方法,自动添加 defer 过程,并通过 PreserveRecover 方法捕获错误。

func do() {
    err := errx.Run(func() {
        panic("oops!")
    })
    fmt.Println(err)
}

# Functions

Describe 返回一个字符串描述给定的错误。如果给定 nil ,返回空字符串。 递归使用 errors.Unwrap() 获取内部错误,并追加在描述信息上。如果错误是 StackfulError ,则描述携带调用栈信息。 若不能获取到对应的信息,则该部分省略。 可通过此方法获取完整的错误链信息。 输出格式为: 最外层错误描述 --- 最外层错误的调用栈信息 === 第1层内部错误的描述 --- 第1层内部错误的调用栈信息 === 第2层内部错误的描述 --- 第2层内部错误的调用栈信息 ...(逐层展示) === 最内层错误的描述 --- 最内层错误的描述的调用栈信息 末尾总是一个空行。.
GetErrorStack 创建一个带有调用栈信息的 ErrorStack 。 调用栈信息使用 runtime.CallersFrames() 获取,skip 参数传递给 runtime.Callers() 。 要跳过当前函数,至少为 2 :分别跳过 runtime.Callers() 和当前函数。.
NewBizError 创建一个 BizError ,给定错误码、错误信息和引起此错误的错误。 cause 指定引发此错误的错误,可以为 nil 。 此方法创建的 BizError 会包含方法调用栈信息。.
NewBizErrorWithoutStack 创建一个 BizError ,给定错误码、错误信息和引起此错误的错误。 cause 指定引发此错误的错误,可以为 nil 。 和 NewBizError() 类似,但不带调用栈信息, BizError.Stack() 返回空字符串。.
PreserveRecover 用于封装从 panic 中 recover 的数据,返回 StackfulError 。 此方法的调用应放在 defer 过程里。.
执行给定的函数。 若函数成功执行,返回 nil ;若函数 panic ,则通过 [PreserveRecover] 捕获并返回对应的错误。.
执行带有一个 error 返回值的的函数。 若函数成功执行,返回函数的返回值;若函数 panic ,则通过 [PreserveRecover] 捕获并返回对应的错误。.
Wrap 封装给定的 error ,返回 StackfulError 。 错误信息的格式为: message: cause.Error() 。若 cause 为 nil,则仅返回 message 。 得到的 StackfulError.Stack() 有一个固定的开头“--- ”,末尾会有一个空行。格式为: --- stack text.
WrapWithoutStack 封装给定的 error 。和 Wrap() 类似,但不带有调用栈信息。 错误信息的格式为: message: cause.Error() 。若 cause 为 nil,则仅返回 message 。.

# Structs

ErrorCause 用于封装一个 error ,表示引起另一个错误的错误,它支持 errors.Unwrap() 。.
ErrorStack 用于存放调用栈信息,以便实现 StackfulError 。 输出调用栈格式为(末尾有一个空行): [file0:line] func0 [file1:line] func1 [file2:line] func2.
ErrorWrapper 是一个 StackfulError ,封装另一个 error ,其表示引起当前错误的原因。.

# Interfaces

BizError 是一个 error 。增加了错误码等属性,可更精确的表示一个预定义的错误。 BizError 实现 StackfulError ,可以携带调用栈信息。 其 Error() 方法不包含调用栈信息和内部错误的信息,目的是隐藏内部细节,仅用于输出业务信息。.
StackfulError 是一个包含调用栈信息的 error 。 通常, Error() 方法返回带有调用栈信息的错误描述。 可通过 ErrorWithoutStack() 获取没有调用栈的错误描述。.