错误组件
gerrors
组件提供了强大、丰富、统一的错误处理能力。该组件也是统一的错误处理组件,如果存在错误返回时,均带有堆栈信息,方便开发者快速定位问题。
常用方法
错误创建
New/Newf
- 说明:用于创建一个自定义错误信息的error对象,并包含堆栈信息。
- 格式:
New(text string) error
Newf(format string, args ...any) error
Wrap/Wrapf
- 说明:用于包裹其他错误error对象,构造成多级的错误信息,包含堆栈信息。
- 格式:
func Wrap(err error, text string) error
func Wrapf(err error, format string, args ...any) error
NewSkip/NewSkipf
- 说明:用于创建一个自定义错误信息的error对象,并且忽略部分堆栈信息(按照当前调用方法位置往上忽略)。高级功能,一般开发者很少用得到。
- 格式:
func NewSkip(skip int, text string) error
func NewSkipf(skip int, format string, args ...any) error
错误码相关方法
func NewCode(code int, text string) error
func NewCodef(code int, format string, args ...any) error
func NewCodeSkip(code, skip int, text string) error
func NewCodeSkipf(code, skip int, format string, args ...any) error
func WrapCode(code int, err error, text string) error
func WrapCodef(code int, err error, format string, args ...any) error
堆栈特性
错误堆栈
标准库的 error
错误实现比较简单,无法进行堆栈追溯,对于产生错误时的上层调用者来讲不是很友好,无法获得错误的调用链详细信息。gerror
支持错误堆栈记录,通过 New/Newf
、Wrap/Wrapf
等方法均会自动记录当前错误产生时的堆栈信息。
示例:
package main
import (
"fmt"
"github.com/camry/g/gerrors/gerror"
)
func OpenFile() error {
return gerror.New("permission denied")
}
func OpenConfig() error {
return gerror.Wrap(OpenFile(), "configuration file opening failed")
}
func ReadConfig() error {
return gerror.Wrap(OpenConfig(), "reading configuration failed")
}
func main() {
fmt.Printf("%+v", ReadConfig())
}
// Output:
// reading configuration failed: configuration file opening failed: permission denied
// 1. reading configuration failed
// 2. configuration file opening failed
// 3. permission denied
// 1). main.OpenFile
// E:/project/Weition/go-example/main.go:10
// 2). main.OpenConfig
// E:/project/Weition/go-example/main.go:14
// 3). main.ReadConfig
// E:/project/Weition/go-example/main.go:18
// 4). main.main
// E:/project/Weition/go-example/main.go:22
可以看到,调用端可以通过Wrap方法将底层的错误信息进行层级叠加,并且包含完整的错误堆栈信息。
HasStack
判断错误是否带堆栈
- 说明:通过
HasStack
方法我们可以判断给定的error
接口对象是否实现(包含)了堆栈信息。 - 格式:
HasStack(err error) bool
- 示例:
func ExampleHasStack() {
err1 := errors.New("sql error")
err2 := gerror.New("write error")
fmt.Println(gerror.HasStack(err1))
fmt.Println(gerror.HasStack(err2))
// Output:
// false
// true
}
Stack
获取堆栈信息
- 说明:通过Stack方法我们可以获得error对象的完整堆栈信息,返回堆栈列表字符串。 注意参数为标准库error类型,当该参数为gerror模块生成的error时, 或者开发者自定义的error对象实现了该接口时支持打印,否则,返回空字符串。
- 格式:
Stack(err error) string
- 示例:
func ExampleStack() {
var err error
err = errors.New("sql error")
err = gerror.Wrap(err, "adding failed")
err = gerror.Wrap(err, "api calling failed")
fmt.Println(gerror.Stack(err))
// Output:
// 1. api calling failed
// 1). main.ExampleStack
// E:/project/Weition/go-example/main.go:14
// 2. adding failed
// 1). main.ExampleStack
// E:/project/Weition/go-example/main.go:13
// 2). main.main
// E:/project/Weition/go-example/main.go:28
// 3. sql error
}
Current
获取当前 error
- 说明:
Current
方法用于获取当前层级的错误信息,通过error
接口对象返回。 - 格式:
Current(err error) error
- 示例:
func ExampleCurrent() {
var err error
err = errors.New("sql error")
err = gerror.Wrap(err, "adding failed")
err = gerror.Wrap(err, "api calling failed")
fmt.Println(err)
fmt.Println(gerror.Current(err))
// Output:
// api calling failed: adding failed: sql error
// api calling failed
}
Next/Unwrap
获取下一层 error
- 说明:
Next/Unwrap
方法用于获取层级错误的下一级错误error
接口对象。当下一层级不存在时,返回nil
。其中Unwrap
方法是Next
方法的别名。 - 格式:
Next(err error) error
- 示例1:简单的错误层级访问示例。
func ExampleNext() {
var err error
err = errors.New("sql error")
err = gerror.Wrap(err, "adding failed")
err = gerror.Wrap(err, "api calling failed")
fmt.Println(err)
err = gerror.Next(err)
fmt.Println(err)
err = gerror.Next(err)
fmt.Println(err)
// Output:
// api calling failed: adding failed: sql error
// adding failed: sql error
// sql error
}
- 示例2:常见遍历逻辑代码示例。
func IsGrpcErrorNotFound(err error) bool {
if err != nil {
for e := err; e != nil; e = gerror.Unwrap(e) {
if s, ok := status.FromError(e); ok && s != nil && s.Code() == codes.NotFound {
return true
}
}
}
return false
}
Cause
获取根错误 error
- 说明:通过
Cause
方法我们可以获得error
对象的根错误信息(原始错误)。 注意参数为标准库error
类型,当该参数为gerror
模块生成的error
时,或者开发者自定义的error
对象实现了该接口方法时支持打印,否则,返回输出的error
对象。 - 格式:
Cause(err error) error
- 示例:
package main
import (
"fmt"
"github.com/camry/g/gerrors/gerror"
)
func OpenFile() error {
return gerror.New("permission denied")
}
func OpenConfig() error {
return gerror.Wrap(OpenFile(), "configuration file opening failed")
}
func ReadConfig() error {
return gerror.Wrap(OpenConfig(), "reading configuration failed")
}
func main() {
fmt.Println(gerror.Cause(ReadConfig()))
}
// Output:
// permission denied
错误比较
Equal
比较方法
错误对象支持比较,主要通过以下方法:
func Equal(err, target error) bool
接口定义
如果自定义的错误数据结构需要支持比较,需要自定义的错误结构实现以下接口:
Equal(target error) bool
使用示例
func ExampleEqual() {
err1 := errors.New("permission denied")
err2 := gerror.New("permission denied")
err3 := gerror.NewCode(gcode.CodeNotAuthorized, "permission denied")
fmt.Println(gerror.Equal(err1, err2))
fmt.Println(gerror.Equal(err2, err3))
// Output:
// true
// false
}
Is
包含判断
错误对象支持包含判断,主要通过以下方法:
func Is(err, target error) bool
接口定义
Is(target error) bool
使用示例
func ExampleIs() {
err1 := errors.New("permission denied")
err2 := gerror.Wrap(err1, "operation failed")
fmt.Println(gerror.Is(err1, err1))
fmt.Println(gerror.Is(err2, err2))
fmt.Println(gerror.Is(err2, err1))
fmt.Println(gerror.Is(err1, err2))
// Output:
// false
// true
// true
// false
}
错误码特性
错误码使用
创建带错误码的 error
NewCode/NewCodef
- 说明:功能同
New/Newf
方法,用于创建一个自定义错误信息的error
对象,并包含堆栈信息,并增加错误码对象的输入。 - 格式:
func NewCode(code gcode.Code, text ...string) error
func NewCodef(code gcode.Code, format string, args ...any) error
- 实例:
func ExampleNewCode() {
err := gerror.NewCode(gcode.New(10000, "", nil), "My Error")
fmt.Println(err.Error())
fmt.Println(gerror.Code(err))
// Output:
// My Error
// 10000
}
func ExampleNewCodef() {
err := gerror.NewCodef(gcode.New(10000, "", nil), "It's %s", "My Error")
fmt.Println(err.Error())
fmt.Println(gerror.Code(err).Code())
// Output:
// It's My Error
// 10000
}
WrapCode/WrapCodef
- 说明:功能同
Wrap/Wrapf
方法,用于包裹其他错误error
对象,构造成多级的错误信息,包含堆栈信息,并增加错误码参数的输入。 - 格式:
func WrapCode(code gcode.Code, err error, text ...string) error
func WrapCodef(code gcode.Code, err error, format string, args ...any) error
- 示例:
func ExampleWrapCode() {
err1 := errors.New("permission denied")
err2 := gerror.WrapCode(gcode.New(10000, "", nil), err1, "Custom Error")
fmt.Println(err2.Error())
fmt.Println(gerror.Code(err2).Code())
// Output:
// Custom Error: permission denied
// 10000
}
func ExampleWrapCodef() {
err1 := errors.New("permission denied")
err2 := gerror.WrapCodef(gcode.New(10000, "", nil), err1, "It's %s", "Custom Error")
fmt.Println(err2.Error())
fmt.Println(gerror.Code(err2).Code())
// Output:
// It's Custom Error: permission denied
// 10000
}
NewCodeSkip/NewCodeSkipf
- 说明:高级方法,开发者一般很少会用到。功能同
NewSkip/NewSkipf
,用于创建一个自定义错误信息的error
对象,并且忽略部分堆栈信息(按照当前调用方法位置往上忽略),并增加错误参数输入。 - 格式:
func NewCodeSkip(code gcode.Code, skip int, text ...string) error
func NewCodeSkipf(code gcode.Code, skip int, format string, args ...any) error
获取 error
中的错误码接口
func Code(err error) gcode.Code
当给定的 error
参数不带有错误码信息时,该方法返回预定义的错误码 gcode.CodeNil
。
错误码接口
基本介绍
提供了默认的错误码组件 gcode
,错误码使用接口化设计,以实现高扩展性。
接口定义
// Code 通用错误代码接口定义。
type Code interface {
// Code 错误码。
Code() int
// Message 错误码简短信息。
Message() string
// Detail 错误码详细信息。
Detail() any
}
默认实现
提供了默认实现 gcode.Code
的结构体,开发者可以直接通过 New/WithCode
方法创建错误码:
- 格式:
func New(code int, message string, detail any) Code
func WithCode(code Code, detail any) Code
- 示例:
func ExampleNew() {
c := gcode.New(1, "custom error", "detailed description")
fmt.Println(c.Code())
fmt.Println(c.Message())
fmt.Println(c.Detail())
// Output:
// 1
// custom error
// detailed description
}
如果开发者觉得默认实现 gcode.Code
的结构体不满足需求,可以自行定义,只需实现 gcode.Code
即可。
错误码扩展
当业务需要复杂的错误码定义时,我们推荐灵活使用错误码的Detail参数来扩展错误码功能。
我们来看个例子。
业务错误码
错误码定义
type BizCode struct {
User User
// ...
}
type User struct {
Id int
Name string
// ...
}
错误码使用
扩展错误码大多数场景下需要使用 WithCode
方法:
func WithCode(code Code, detail any) Code
因此上面我们的自定义扩展可以这么使用:
code := gcode.WithCode(gcode.CodeNotFound, BizCode{
User: User{
Id: 1,
Name: "John",
},
})
fmt.Println(code)
即在错误码中我们可以根据业务场景注入一些自定义的错误码扩展数据,以方便上层获取错误码后做进一步处理。
错误码实现
当业务需要更复杂的错误码定义时,我们可以自定义实现业务自己的错误码,只需要实现gcode.Code相关的接口即可。
我们来看个例子。
自定义错误码
定义结构体并实现 gcode.code
接口定义的方法
type BizCode struct {
code int
message string
detail BizCodeDetail
}
type BizCodeDetail struct {
Code string
HttpCode int
}
func (c BizCode) BizDetail() BizCodeDetail {
return c.detail
}
func (c BizCode) Code() int {
return c.code
}
func (c BizCode) Message() string {
return c.message
}
func (c BizCode) Detail() interface{} {
return c.detail
}
func New(httpCode int, code string, message string) gcode.Code {
return BizCode{
code: 0,
message: message,
detail: BizCodeDetail{
Code: code,
HttpCode: httpCode,
},
}
}
定义业务错误码
var (
CodeNil = New(200, "OK", "")
CodeNotFound = New(404, "Not Found", "Resource does not exist")
CodeInternal = New(500, "Internal Error", "An error occurred internally")
// ...
)
内置错误码
框架提供了常见的一些错误码定义,开发者可直接使用:
https://github.com/camry/g/blob/main/gerrors/gcode/gcode.go
其它特性
NewOption
自定义的错误创建
- 说明:用于自定义配置的错误对象创建。
- 格式:
func NewOption(option Option) error
- 示例:
func ExampleNewOption() {
err := gerror.NewOption(gerror.Option{
Text: "this feature is disabled in this storage",
Code: gcode.CodeNotSupported,
})
}
fmt
格式化
通过以上示例我们可以看到,通过 %+v
的打印格式可以打印出完整的堆栈信息,当然 gerror.Error
对象支持多种 fmt
格式:
格式符 | 输出内容 |
---|---|
%v ,%s |
打印所有的层级错误信息,构成完成的字符串返回,多个层级使用 : 拼接。 |
%-v , %-s |
打印当前层级的错误信息,返回字符串。 |
%+s |
打印完整的堆栈信息列表。 |
%+v |
打印所有的层级错误信息字符串,以及完整的堆栈信息,等同于 %s\n%+s 。 |
使用示例:
package main
import (
"errors"
"fmt"
"github.com/camry/g/gerrors/gerror"
)
func main() {
var err error
err = errors.New("sql error")
err = gerror.Wrap(err, "adding failed")
err = gerror.Wrap(err, "api calling failed")
fmt.Printf(" %%s: %s\n", err)
fmt.Printf("%%-s: %-s\n", err)
fmt.Println("%+s: ")
fmt.Printf("%+s\n", err)
}
// Output:
// %s: api calling failed: adding failed: sql error
// %-s: api calling failed
// %+s:
// 1. api calling failed
// 1). main.main
// E:/project/Weition/go-example/main.go:14
// 2. adding failed
// 1). main.main
// E:/project/Weition/go-example/main.go:13
// 3. sql error
日志输出支持
glog
日志管理模块天然支持对 gerror
错误堆栈打印支持,这种支持不是强耦合性的,而是通过 fmt
格式化打印接口支持的。
使用示例:
package main
import (
"errors"
"github.com/camry/g/gerrors/gerror"
"github.com/camry/g/glog"
)
func main() {
var err error
err = errors.New("sql error")
err = gerror.Wrap(err, "adding failed")
err = gerror.Wrap(err, "api calling failed")
glog.Errorf("%+v", err)
}
// Output:
// ERROR msg=api calling failed: adding failed: sql error
// 1. api calling failed
// 1). main.main
// E:/project/Weition/go-example/main.go:13
// 2. adding failed
// 1). main.main
// E:/project/Weition/go-example/main.go:12
// 3. sql error
性能测试
常用方法的基准性能测试:https://github.com/camry/g/blob/main/gerrors/gerror/gerror_z_bench_test.go
goos: windows
goarch: amd64
pkg: github.com/camry/g/gerrors/gerror
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
Benchmark_New
Benchmark_New-12 1722652 696.1 ns/op
Benchmark_Newf
Benchmark_Newf-12 1352446 877.1 ns/op
Benchmark_Wrap
Benchmark_Wrap-12 1471184 816.5 ns/op
Benchmark_Wrapf
Benchmark_Wrapf-12 1323144 894.9 ns/op
Benchmark_NewSkip
Benchmark_NewSkip-12 1754803 686.0 ns/op
Benchmark_NewSkipf
Benchmark_NewSkipf-12 1373864 872.4 ns/op
Benchmark_NewCode
Benchmark_NewCode-12 1431099 828.0 ns/op
Benchmark_NewCodef
Benchmark_NewCodef-12 1301948 919.9 ns/op
Benchmark_NewCodeSkip
Benchmark_NewCodeSkip-12 1402771 859.0 ns/op
Benchmark_NewCodeSkipf
Benchmark_NewCodeSkipf-12 1293831 918.0 ns/op
Benchmark_WrapCode
Benchmark_WrapCode-12 1402704 856.2 ns/op
Benchmark_WrapCodef
Benchmark_WrapCodef-12 1327546 902.1 ns/op
PASS