Package errors 添加了对go中的错误的堆栈跟踪支持,以及对 error code 和 message 的支持
不返回给客户端的错误,通常用于日志记录,可以使用 ErrorMessage
使用errors.New()
、errors.NewWithMessage()
预定义错误,
在调用处使用errors.WithStack()
调用
package main
import (
"fmt"
"github.com/ace-zhaoy/errors"
"os"
)
var (
ErrInvalidParams = errors.NewWithMessage("invalid params")
)
func OpenFile() error {
filePath := "./test.err"
_, err := os.Open(filePath)
if err != nil {
err = ErrInvalidParams.Wrapf(err, "param: %s", filePath)
}
return err
}
func main() {
err := OpenFile()
// 打印堆栈信息
fmt.Printf("%+v\n", err)
}
// Output:
// open ./test.err: no such file or directory
// invalid params
// param: ./test.err
// main.OpenFile
// /go/src/test/err/main.go:17
// main.main
// /go/src/test/err/main.go:23
// runtime.main
// /usr/local/go/src/runtime/proc.go:250
// runtime.goexit
// /usr/local/go/src/runtime/asm_amd64.s:1571
直接在调用处使用 errors.NewWithStack
package main
import (
"fmt"
"github.com/ace-zhaoy/errors"
)
func Service() error {
err := errors.NewWithStack("sql err: %s", "sql 123")
return err
}
func main() {
err := Service()
// 打印堆栈信息
fmt.Printf("%+v\n", err)
}
// Output:
// sql err: sql 123
// main.Service
// /go/src/test/err/main.go:9
// main.main
// /go/src/test/err/main.go:14
// runtime.main
// /usr/local/go/src/runtime/proc.go:250
// runtime.goexit
// /usr/local/go/src/runtime/asm_amd64.s:1571
通常用于识别特殊错误,或者给客户端返回特殊错误码
一般先预定义,在需要的地方调用。需要注意,预定义没有堆栈跟踪,需要使用
package main
import (
"fmt"
"github.com/ace-zhaoy/errors"
)
const (
CodeInvalidToken = 401100000
CodeInvalidParams = 400102030
CodeUserNotFound = 500201010
CodeOrderNotExists = 500302001
)
var (
ErrCodeInvalidToken = errors.NewWithCode(CodeInvalidToken, "invalid token")
ErrCodeInvalidParams = errors.NewWithCode(CodeInvalidParams, "invalid params")
ErrCodeUserNotFound = errors.NewWithCode(CodeUserNotFound, "user not found")
ErrCodeOrderNotExists = errors.NewWithCode(CodeOrderNotExists, "order not exists")
)
func SqlFirst() error {
// record not found
// err := first()
err := errors.New("record not found")
return ErrCodeUserNotFound.Wrapf(err, "param: id: %d", 1)
}
func Server() error {
err := SqlFirst()
if err != nil {
if errors.Is(err, ErrCodeUserNotFound) {
err = ErrCodeInvalidParams.WrapStack(err)
}
return err
}
// other code
return nil
}
func main() {
err := Server()
codeErr := errors.LatestCode(err)
fmt.Printf("error code: %d\n", codeErr.Code())
fmt.Printf("error msg: %s\n", codeErr.Message())
fmt.Printf("error err: %s\n", codeErr)
fmt.Println("---------------------")
fmt.Printf("%+v\n", err)
}
// Output:
// error code: 400102030
// error msg: invalid params
// error err: [400102030: invalid params] -> {param: id: 1 -> {[500201010: user not found] -> {record not found}}}
// ---------------------
// record not found
// 500201010: user not found
// param: id: 1
// main.SqlFirst
// /go/src/test/err/main.go:27
// main.Server
// /go/src/test/err/main.go:31
// main.main
// /go/src/test/err/main.go:43
// runtime.main
// /usr/local/go/src/runtime/proc.go:250
// runtime.goexit
// /usr/local/go/src/runtime/asm_amd64.s:1571
// 400102030: invalid params
返回的错误应该使用官方的 error
接口
type error interface {
Error() string
}
如果不清楚接收到的 error 是否有堆栈跟踪,可以使用 errors.WithStack()
进行包装
该方法会检测是否有堆栈跟踪,且不会重复增加堆栈跟踪
也可以使用 errors.Wrap()
,在增加堆栈跟踪的同时增加 message 消息
package main
import (
"fmt"
"github.com/ace-zhaoy/errors"
"os"
)
func OpenFile() error {
_, err := os.Open("./test.err")
// 无需判断 nil,自动添加堆栈
return errors.WithStack(err)
}
func Service() error {
err := OpenFile()
if err != nil {
return err
}
// ...
return nil
}
func main() {
err := Service()
// 打印堆栈信息
fmt.Printf("%+v\n", err)
}
比如,github.com/jinzhu/gorm
未找到记录时会返回 gorm.ErrRecordNotFound
,
github.com/redis/go-redis/v9
未找到记录会返回 redis.Nil
。
可以在项目中预定义一个未找到记录的 error,将这种错误统一包裹
package main
import (
"fmt"
"github.com/ace-zhaoy/errors"
"github.com/jinzhu/gorm"
"github.com/redis/go-redis/v9"
)
var (
ErrNotFount = errors.NewWithMessage("not found")
)
func sqlFirst() error {
// first 未找到记录
err := gorm.ErrRecordNotFound
if err != nil {
// 统一用项目预定义错误包裹
return ErrNotFount.Wrapf(err, "id: %d", 123)
}
// ...
return nil
}
func redisFirst() error {
// Get 未找到记录
var err error = redis.Nil
if err != nil {
// 统一用项目预定义错误包裹
return ErrNotFount.Wrapf(err, "key: %d", 456)
}
// ...
return nil
}
func main() {
err1 := sqlFirst()
if err1 != nil {
if errors.Is(err1, ErrNotFount) {
// 打印堆栈信息
fmt.Printf("%+v\n", err1)
}
// other code
//return
}
err2 := redisFirst()
if err2 != nil {
if errors.Is(err2, ErrNotFount) {
// 打印堆栈信息
fmt.Printf("%+v\n", err2)
}
// other code
return
}
}
子函数(方法)参数 不包含于 父函数(方法)参数时,应将子函数(方法)的参数作为 message 返回,反之则不用
package main
import (
"fmt"
"github.com/ace-zhaoy/errors"
)
func A(a int) error {
return errors.NewWithStack("a err")
}
func B(b, c int) error {
return errors.NewWithStack("a err")
}
func C() int {
return 1
}
func service1(a int) error {
err := A(a)
if err != nil {
// A 的参数包含于 service1 的参数
return err
}
// other code
return nil
}
func service2(b int) error {
c := C()
err := B(b, c)
if err != nil {
// B 的参数多了 c
return errors.Wrap(err, "b: %v, c: %v", b, c)
}
// other code
return nil
}
func main() {
err1 := service1(1)
fmt.Printf("%+v\n", err1)
err2 := service2(2)
fmt.Printf("%+v\n", err2)
}
无需返回 error 时,应记录到日志中,反之不用每个 error 都记录日志
package main
import (
"fmt"
"github.com/ace-zhaoy/errors"
"log"
)
func A() error {
return errors.NewWithStack("a err")
}
func service() error {
err := A()
if err != nil {
// 不返回错误时,需要将错误记录到日志中,反之不用每个 error 都记录日志
log.Printf("%+v", err)
err = nil
return err
}
// other code
return nil
}
func main() {
err1 := service()
fmt.Printf("%+v\n", err1)
}
之前判断错误通常使用下面形式
func A() error {
// ...
err := B()
if err != nil {
return err
}
// ...
err = C()
if err != nil {
return err
}
}
现在可以这么用
func A() (err error) {
defer errors.Recover(func(e error) {
err = e
})
err := B()
errors.Check(err)
// If err is not nil, the code that follows will not execute
// ...
err = C()
errors.Check(err)
return nil
}
err1 := errors.New("error 1")
err2 := errors.New("error 2")
err := errors.Join(err1, err2)
fmt.Println(errors.Is(err, err1))
// output: true
fmt.Println(errors.Is(err, err2))
// output: true
err := errors.NewWithJoin()
err1 := errors.New("error 1")
err2 := errors.New("error 2")
err.Append(err1)
err.Append(err2)
fmt.Println(errors.Is(err, err1))
// output: true
fmt.Println(errors.Is(err, err2))
// output: true