pkg/errors #
github.com/pkg/errors 是 Go 语言中一个广泛使用的错误处理库,它通过**错误包装(Error Wrapping)和堆栈追踪(Stack Trace)**功能,显著增强了错误信息的可读性和调试效率。虽然不是 Go 官方包,但却被很多团队当作事实标准来使用。
Go 语言的错误处理非常简单,秉承着大道至简风格。不过也正是由于简单,也就暴露了 Go 错误处理的些许简陋。
在日常开发中,我们对于错误处理会有两个最普遍的诉求:
- 附加错误信息:在拿到原有的底层代码或第三方库返回的错误后,我们可能希望附加一些业务信息,比如
userID,这样就知道这条错误是由哪个用户产生的。 - 附加错误堆栈:因为错误堆栈中有出错代码的位置,以及整个调用链路,这会方便我们定位问题。
功能概览 #
- 错误包装:将底层错误包裹在新错误中,保留原始错误链。
- 堆栈追踪:自动捕获错误发生时的调用堆栈,精准定位问题源头。
- 因果链提取:通过
errors.Cause()穿透包装层,直接获取原始错误。 - 格式化输出:支持自定义错误消息和堆栈打印格式。
安装 #
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))