golang内置错误处理增强与自定义错误类型插件库errors的使用
Golang内置错误处理增强与自定义错误类型插件库errors的使用
Errors包是Go内置errors包的替代品,它允许您创建14种不同类型的错误,涵盖了大多数用例。其中一些对于Web应用程序来说可能过于具体,但仍然很有用。
功能特性
- 支持14种错误类型
- 轻松处理用户友好消息
- 堆栈跟踪 - 格式化、未格式化、自定义格式
- 获取堆栈跟踪的程序计数器
- 使用
errors.RuntimeFrames(err error)
获取堆栈跟踪的runtime.Frames - 所有错误类型都支持HTTP状态码和用户友好消息(包装的消息会被连接)
- 所有错误类型都支持GRPC状态码和用户友好消息(包装的消息会被连接)
- 生成每种错误类型的辅助函数
- 辅助函数用于获取错误类型、错误类型作为整数、检查错误类型是否在链中的任何位置被包装
- 支持
fmt.Formatter
对于嵌套错误,消息和错误也会遍历整个错误链。
可用的错误类型
- TypeInternal - 内部系统错误,例如数据库错误
- TypeValidation - 验证错误,例如无效的电子邮件地址
- TypeInputBody - 无效的输入数据,例如无效的JSON
- TypeDuplicate - 重复内容错误,例如用户已存在(尝试注册新用户时)
- TypeUnauthenticated - 未认证错误
- TypeUnauthorized - 未授权访问错误
- TypeEmpty - 当预期的非空资源为空时
- TypeNotFound - 预期资源未找到,例如用户ID未找到
- TypeMaximumAttempts - 尝试同一操作超过允许的阈值
- TypeSubscriptionExpired - 当用户的"付费"账户已过期时
- TypeDownstreamDependencyTimedout - 当下游依赖服务请求超时时
- TypeNotImplemented - 当请求的功能由于能力不足而无法完成时
- TypeContextTimedout - 当Go上下文超时时
- TypeContextCancelled - 当Go上下文被取消时
用户友好消息
在编写API时,我们通常希望用更容易理解的用户友好消息来响应。而不是返回原始错误,只需记录原始错误。
对于所有错误类型都有辅助函数。当需要为现有错误设置友好消息时,有带有后缀’Err’的辅助函数。所有这些辅助函数都接受原始错误和一个字符串。
package main
import (
"fmt"
"github.com/naughtygopher/errors"
)
func Bar() error {
return fmt.Errorf("hello %s", "world!")
}
func Foo() error {
err := Bar()
if err != nil {
return errors.InternalErr(err, "bar is not happy")
}
return nil
}
func main() {
err := Foo()
fmt.Println("err:", err)
fmt.Println("\nerr.Error():", err.Error())
fmt.Printf("\nformatted +v: %+v\n", err)
fmt.Printf("\nformatted v: %v\n", err)
fmt.Printf("\nformatted +s: %+s\n", err)
fmt.Printf("\nformatted s: %s\n", err)
_, msg, _ := errors.HTTPStatusCodeMessage(err)
fmt.Println("\nmsg:", msg)
}
输出:
err: bar is not happy
err.Error(): /Users/k.balakumaran/go/src/github.com/naughtygopher/errors/cmd/main.go:16: bar is not happy
hello world!bar is not happy
formatted +v: /Users/k.balakumaran/go/src/github.com/naughtygopher/errors/cmd/main.go:16: bar is not happy
hello world!bar is not happy
formatted v: bar is not happy
formatted +s: bar is not happy: hello world!
formatted s: bar is not happy
msg: bar is not happy
文件路径和行号前缀
Go错误的一个常见烦恼是找出错误的来源,特别是在有嵌套函数调用时。注释通过能够为错误提供上下文消息有很大帮助。例如fmt.Errorf("database query returned error %w", err)
。然而在这个包中,Error() string
函数(Go错误接口方法)打印带有文件路径和行号前缀的错误。它看起来像../Users/JohnDoe/apps/main.go:50 hello world
,其中’hello world’是错误消息。
HTTP/GRPC状态码和消息
函数errors.HTTPStatusCodeMessage(error) (int, string, bool)
和errors.GRPCStatusCodeMessage(error) (int, string, bool)
返回HTTP/GRPC状态码、消息和布尔值。如果错误是此包中的*Error类型,则布尔值为true。如果错误是嵌套的,它会解包并返回单个连接的消息。
如何使用?
除了在"用户友好消息"部分解释的函数外,下面提供了更多示例。
package main
import (
"fmt"
"net/http"
"time"
"github.com/naughtygopher/errors"
"github.com/naughtygopher/webgo/v6"
"github.com/naughtygopher/webgo/v6/middleware/accesslog"
)
func bar() error {
return fmt.Errorf("%s %s", "sinking", "bar")
}
func bar2() error {
err := bar()
if err != nil {
return errors.InternalErr(err, "bar2 was deceived by bar1 :(")
}
return nil
}
func foo() error {
err := bar2()
if err != nil {
return errors.InternalErr(err, "we lost bar2!")
}
return nil
}
func handler(w http.ResponseWriter, r *http.Request) {
err := foo()
if err != nil {
// 在服务器上记录错误以便故障排除
fmt.Println(err.Error())
// 使用友好消息响应请求
status, msg, _ := errors.HTTPStatusCodeMessage(err)
webgo.SendError(w, msg, status)
return
}
webgo.R200(w, "yay!")
}
func routes() []*webgo.Route {
return []*webgo.Route{
{
Name: "home",
Method: http.MethodGet,
Pattern: "/",
Handlers: []http.HandlerFunc{
handler,
},
},
}
}
func main() {
router := webgo.NewRouter(&webgo.Config{
Host: "",
Port: "8080",
ReadTimeout: 15 * time.Second,
WriteTimeout: 60 * time.Second,
}, routes()...)
router.UseOnSpecialHandlers(accesslog.AccessLog)
router.Use(accesslog.AccessLog)
router.Start()
}
使用webgo来说明函数errors.HTTPStatusCodeMessage
的用法。它返回适当的http状态码、存储的用户友好消息和布尔值。如果返回的错误是*Error类型,则布尔值为true。
一旦应用程序运行,您可以通过在浏览器中打开http://localhost:8080
来检查响应。或者在终端上:
$ curl http://localhost:8080
{"errors":"we lost bar2!. bar2 was deceived by bar1 :(","status":500} // 输出
而fmt.Println(err.Error())
在stdout上生成的输出将是:
/Users/username/go/src/errorscheck/main.go:28 /Users/username/go/src/errorscheck/main.go:20 sinking bar
基准测试 [2025-07-03]
Macbook Air 13-inch, M3, 2024, 内存: 24 GB
$ go version
go version go1.24.4 darwin/arm64
$ go test -benchmem -bench .
goos: darwin
goarch: arm64
pkg: github.com/naughtygopher/errors
cpu: Apple M3
Benchmark_Internal-8 3650916 321.7 ns/op 1104 B/op 2 allocs/op
Benchmark_Internalf-8 3155463 378.9 ns/op 1128 B/op 3 allocs/op
Benchmark_InternalErr-8 3866085 312.2 ns/op 1104 B/op 2 allocs/op
Benchmark_InternalGetError-8 1983544 608.0 ns/op 1576 B/op 6 allocs/op
Benchmark_InternalGetErrorWithNestedError-8 2419369 497.8 ns/op 1592 B/op 6 allocs/op
Benchmark_InternalGetMessage-8 3815074 316.1 ns/op 1104 B/op 2 allocs/op
Benchmark_InternalGetMessageWithNestedError-8 3470449 342.2 ns/op 1128 B/op 3 allocs/op
Benchmark_HTTPStatusCodeMessage-8 40540940 29.12 ns/op 16 B/op 1 allocs/op
BenchmarkHasType-8 100000000 11.44 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/naughtygopher/errors 13.805s
贡献
更多的错误类型、自定义、功能、多错误;欢迎PR和问题!
Gopher
这里使用的gopher是使用Gopherize.me创建的。像我们的gopher女士一样对Go错误表示一些爱!
更多关于golang内置错误处理增强与自定义错误类型插件库errors的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang内置错误处理增强与自定义错误类型插件库errors的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 错误处理增强与自定义错误类型
Go 语言内置的错误处理机制相对简单,但通过标准库 errors
和第三方库如 pkg/errors
,我们可以实现更强大的错误处理功能。下面我将介绍 Go 的错误处理增强方法和自定义错误类型的使用。
内置错误处理基础
Go 使用简单的 error
接口作为错误处理的基础:
type error interface {
Error() string
}
基本用法:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}
自定义错误类型
我们可以创建更丰富的错误类型:
type MyError struct {
Code int
Message string
Op string
}
func (e *MyError) Error() string {
return fmt.Sprintf("%s: code=%d, msg=%s", e.Op, e.Code, e.Message)
}
func process() error {
return &MyError{
Code: 500,
Message: "internal server error",
Op: "process",
}
}
func main() {
err := process()
if err != nil {
fmt.Println("Error:", err)
// 类型断言获取更多信息
if me, ok := err.(*MyError); ok {
fmt.Printf("Details: Code=%d, Op=%s\n", me.Code, me.Op)
}
}
}
使用 pkg/errors
增强错误处理
pkg/errors
提供了堆栈跟踪和错误包装功能:
import (
"github.com/pkg/errors"
)
func readFile(path string) ([]byte, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.Wrap(err, "read failed")
}
return data, nil
}
func main() {
_, err := readFile("/nonexistent/file")
if err != nil {
// 打印带堆栈的错误信息
fmt.Printf("%+v\n", err)
// 获取原始错误
original := errors.Cause(err)
fmt.Println("Original error:", original)
}
}
Go 1.13+ 的错误增强
Go 1.13 引入了错误包装和检查的新方法:
func processFile() error {
if err := readConfig(); err != nil {
return fmt.Errorf("config error: %w", err)
}
return nil
}
func main() {
err := processFile()
if err != nil {
// 检查特定错误类型
var configErr *ConfigError
if errors.As(err, &configErr) {
fmt.Println("Config error:", configErr)
}
// 解包错误链
unwrapped := errors.Unwrap(err)
fmt.Println("Unwrapped error:", unwrapped)
}
}
最佳实践
-
错误定义:在包级别定义可导出的错误变量
var ( ErrNotFound = errors.New("not found") ErrInvalidInput = errors.New("invalid input") )
-
错误包装:使用
%w
包装底层错误,保留原始错误信息 -
错误检查:使用
errors.Is
和errors.As
代替直接比较 -
添加上下文:在错误传递过程中添加有意义的上下文信息
-
日志记录:在错误处理的最上层记录完整的错误信息
完整示例
package main
import (
"errors"
"fmt"
"os"
)
// 自定义错误类型
type ApiError struct {
Status int
Message string
}
func (e *ApiError) Error() string {
return fmt.Sprintf("API error %d: %s", e.Status, e.Message)
}
func fetchData(id string) (string, error) {
if id == "" {
return "", &ApiError{Status: 400, Message: "invalid ID"}
}
if id == "404" {
return "", fmt.Errorf("data fetch: %w", os.ErrNotExist)
}
return "data for " + id, nil
}
func main() {
data, err := fetchData("404")
if err != nil {
// 检查特定错误类型
var apiErr *ApiError
if errors.As(err, &apiErr) {
fmt.Printf("API Error: Status=%d\n", apiErr.Status)
}
// 检查解包后的错误
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Data not found")
}
fmt.Printf("Full error: %v\n", err)
return
}
fmt.Println("Data:", data)
}
通过以上方法,你可以构建更健壮、更易于调试的 Go 应用程序错误处理系统。