跳过正文
  1. 文章/
  2. GoLang/
  3. 常用包/
  4. 第三方包/

11、errors

·955 字·2 分钟· loading · loading · ·
GoLang 常用包 第三方包
GradyYoung
作者
GradyYoung
第三方包 - 点击查看当前系列文章
§ 11、errors 「 当前文章 」

pkg/errors
#

github.com/pkg/errors 是 Go 语言中一个广泛使用的错误处理库,它通过**错误包装(Error Wrapping)堆栈追踪(Stack Trace)**功能,显著增强了错误信息的可读性和调试效率。虽然不是 Go 官方包,但却被很多团队当作事实标准来使用。

Go 语言的错误处理非常简单,秉承着大道至简风格。不过也正是由于简单,也就暴露了 Go 错误处理的些许简陋。

在日常开发中,我们对于错误处理会有两个最普遍的诉求:

  1. 附加错误信息:在拿到原有的底层代码或第三方库返回的错误后,我们可能希望附加一些业务信息,比如 userID,这样就知道这条错误是由哪个用户产生的。
  2. 附加错误堆栈:因为错误堆栈中有出错代码的位置,以及整个调用链路,这会方便我们定位问题。

功能概览
#

  1. 错误包装:将底层错误包裹在新错误中,保留原始错误链。
  2. 堆栈追踪:自动捕获错误发生时的调用堆栈,精准定位问题源头。
  3. 因果链提取:通过 errors.Cause() 穿透包装层,直接获取原始错误。
  4. 格式化输出:支持自定义错误消息和堆栈打印格式。

安装
#

go get -u github.com/pkg/errors

基础用法
#

创建错误
#

import "github.com/pkg/errors"

// 基础错误
err := errors.New("database connection failed")

// 格式化错误(类似 fmt.Errorf)
err = errors.Errorf("invalid input: %s", "null")

包装错误
#

使用场景:底层错误(如文件读取)被上层业务逻辑包裹

// 包裹错误并附加上下文
func readConfig(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        return errors.Wrapf(err, "failed to read config file %q", path)
    }
    // ...
}

提取原始错误
#

func process() error {
    err := readConfig("/invalid/path")
    if err != nil {
        return errors.Wrap(err, "processing failed")
    }
    return nil
}

func main() {
    err := process()
    if err != nil {
        // 穿透所有包装层,获取最底层的原始错误
        cause := errors.Cause(err)
        fmt.Println("Root cause:", cause) // 输出: failed to open /invalid/path: no such file...
    }
}

自定义错误消息与堆栈
#

// 附加堆栈信息(通常由 Wrap/Wrapf 自动处理)
err := errors.WithStack(err)

// 附加自定义消息(不覆盖原始错误)
err = errors.WithMessage(err, "permission denied")

高级特性
#

堆栈打印格式化
#

// 打印完整错误链和堆栈
fmt.Printf("%+v\n", err)

// 输出示例:
// main.process
//     /project/main.go:20
// main.readConfig
//     /project/config.go:12
// os.Open
//     /usr/local/go/src/os/os.go:345
// ...

类型断言与自定义错误
#

// 定义自定义错误类型
type MyError struct {
    Code    int
    Message string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

// 包装自定义错误
func apiCall() error {
    return errors.Wrap(&MyError{Code: 500, Message: "API error"}, "request failed")
}

// 使用时类型断言
if err := apiCall(); err != nil {
    if myErr, ok := errors.Cause(err).(*MyError); ok {
        fmt.Println("Error Code:", myErr.Code)
    }
}

最佳实践
#

  • 分层包装错误:

    • 底层库:使用 errors.Wrap 包裹系统错误(如 os.Open)。

    • 业务层:用 errors.Wrapf 添加业务上下文(如参数值)。

    • 顶层入口:最终通过 errors.Cause 提取根因。

  • 避免过度包装:

    • 不要在循环中重复包装相同错误,会导致错误链爆炸。

    • 仅在关键路径添加有价值的上下文。

  • 敏感信息过滤:

    • 包装错误时避免包含密码、密钥等敏感数据。

    • 可通过 errors.WithMessagef 动态过滤:

errors.WithMessagef(err, "user=%s", maskSensitiveData(userID))
第三方包 - 点击查看当前系列文章
§ 11、errors 「 当前文章 」