Gin框架提供了强大而灵活的错误处理机制,包括内置的错误管理、panic恢复、中间件支持等功能。通过合理使用这些机制,我们可以构建出健壮、可维护的Web应用程序。在实际开发中,应根据项目需求设计合适的错误处理策略,确保错误能够被正确捕获、处理和记录。
本文将全面详细地介绍Gin框架中的错误处理机制以及相关的方法。
1. 错误处理基础
1.1. gin.Error
1.1.1. gin.Error 类型
Gin框架提供了一个内置的gin.Error结构体来处理错误。这个结构体不仅包含错误信息本身,还包含错误类型和元数据,使开发者能够更好地管理错误。
类型源码:
// gin.Error 定义了一种错误的格式
type Error struct {
Err error // error
Type ErrorType // 错误类型
Meta any // 元数据
}
// errorMsgs 定义了gin.Error数组类型
type errorMsgs []*Error
1.1.2. 错误类型
gin.Error中的ErrorType属性用于标识错误的类型。
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
type ErrorType uint64
const (
// ErrorTypeBind is used when Context.Bind() fails.
ErrorTypeBind ErrorType = 1 << 63
// ErrorTypeRender is used when Context.Render() fails.
ErrorTypeRender ErrorType = 1 << 62
// ErrorTypePrivate indicates a private error.
ErrorTypePrivate ErrorType = 1 << 0
// ErrorTypePublic indicates a public error.
ErrorTypePublic ErrorType = 1 << 1
// ErrorTypeAny indicates any other error.
ErrorTypeAny ErrorType = 1<<64 - 1
// ErrorTypeNu indicates any other error.
ErrorTypeNu = 2
)
gin使用位标志来区分不同类型的错误,通过位运算实现错误类型的组合和判断!
内置的错误类型:
ErrorTypeBind:绑定数据时发生的错误ErrorTypeRender:渲染响应时发生的错误ErrorTypePrivate:私有错误类型ErrorTypePublic:公共错误类型ErrorTypeAny:任意错误类型ErrorTypeNu: 备用的错误类型
1.1.3. 类型方法
gin.Error类型的方法如下:
(msg *Error) SetType(flags ErrorType) *Error:设置错误类型。(msg *Error) SetMeta(data any) *Error:设置错误的元数据。(msg *Error) JSON() any:创建一个适合格式化的JSON对象。(msg *Error) MarshalJSON() ([]byte, error):序列化成JSON字符串。(msg Error) Error() string:获取错误字符串。(msg *Error) IsType(flags ErrorType) bool:判断错误是否属于指定的错误类型。(msg Error) Unwrap() error:获取原始错误。
errorMsgs类型的方法如下:
(a errorMsgs) ByType(typ ErrorType) errorMsgs:筛选指定错误类型的错误。(a errorMsgs) Last() *Error:获取最后一个错误。(a errorMsgs) Errors() []string:获取错误字符串数组。(a errorMsgs) JSON() any:创建一个适合格式化的JSON对象。(a errorMsgs) MarshalJSON() ([]byte, error):序列化成JSON字符串。(a errorMsgs) String() string:获取错误字符串。
1.2. 上下文中的错误列表
在gin.Context结构体中,包含一个Errors errorMsgs属性,用于保存所有处理器/中间件附加到上下文的错误列表。
type Context struct {
...
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
...
}
1.3. 向上下文中添加错误
在处理请求时,可以使用Context.Error()方法向错误列表中添加错误:
方法源码:
// Error 用于往上下文错误列表中添加错误
func (c *Context) Error(err error) *Error {
if err == nil {
panic("err is nil")
}
// 判断error是否为gin.Error类型
var parsedError *Error
ok := errors.As(err, &parsedError)
if !ok {
// 如果不是就包装成一个gin.Error类型
parsedError = &Error{
Err: err,
Type: ErrorTypePrivate,
}
}
// 添加到错误列表
c.Errors = append(c.Errors, parsedError)
return parsedError
}
示例:
func handler(c *gin.Context) {
if err := someOperation(); err != nil {
c.Error(err) // 添加错误到错误列表
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"status": "success"})
}
1.4. 获取错误列表
通过Context.Errors可以获取当前请求的所有错误:
func handler(c *gin.Context) {
// ... 一些操作 ...
if len(c.Errors) > 0 {
// 处理错误
for _, e := range c.Errors {
log.Printf("Error: %v, Type: %v, Meta: %v", e.Err, e.Type, e.Meta)
}
}
}
2. 错误处理实践
2.1. 错误处理中间件
为了避免重复编写错误处理代码,我们可以将通用的错误处理逻辑写到中间件中。
错误处理中间件示例:
// ErrorsMiddleware 错误处理中间件
func ErrorsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 先执行后续处理函数
c.Next()
// 错误检查
if len(c.Errors) == 0 {
return
}
// 只处理最后一个错误
lastError := c.Errors.Last()
switch lastError.Type {
case gin.ErrorTypeBind:
c.JSON(http.StatusBadRequest, gin.H{
"error": "Binding Error",
"detail": lastError.Error(),
})
case gin.ErrorTypeRender:
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Render Error",
"detail": lastError.Error(),
})
default:
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"detail": lastError.Error(),
})
}
}
}
业务逻辑示例:
func TestError(c *gin.Context) {
param := new(Param)
if err := c.ShouldBind(param); err != nil {
_ = c.Error(&gin.Error{
Err: err,
Type: gin.ErrorTypeBind,
})
return
}
c.JSON(200, gin.H{
"name": param.Name,
"age": param.Age,
})
}
2.2. 自定义错误类型
gin框架提供的错误类型并不总能满足我们的业务需求,我们可以定义自己的错误类型和处理逻辑。
比如我们可以定义如下的一个错误类型:
// AppError 错误结构体
type AppError struct {
status int // 状态码
Code int `json:"code"` // 错误码
Msg string `json:"msg"` // 错误信息
}
func (err *AppError) Error() string {
return fmt.Sprintf("status: %d, code: %d, msg: %s", err.status, err.Code, err.Msg)
}
错误中间件示例:
// ErrorsMiddleware 错误处理中间件
func ErrorsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 先执行后续处理函数
c.Next()
// 错误检查
if len(c.Errors) == 0 {
return
}
// 只处理最后一个错误
lastError := c.Errors.Last()
var appError *model.AppError
if errors.As(lastError.Err, &appError) {
c.JSON(appError.Status, gin.H{
"code": appError.Code,
"message": appError.Msg,
})
} else {
c.JSON(500, gin.H{
"code": "E0000",
"message": "Internal System Error",
})
}
}
}
业务逻辑示例:
func TestError(c *gin.Context) {
param := new(Param)
// 绑定参数
if err := c.ShouldBind(param); err != nil {
var validatorErrs validator.ValidationErrors
if errors.As(err, &validatorErrs) {
// 如果是校验出错,返回V0001
_ = c.Error(&model.AppError{
Status: 400,
Code: "V0001",
Msg: validatorErrs.Error(),
})
} else {
// 其他错误返回V0000
_ = c.Error(&model.AppError{
Status: 400,
Code: "V0000",
Msg: err.Error(),
})
}
return
}
c.JSON(200, gin.H{
"name": param.Name,
"age": param.Age,
})
}
2.3. 自定义错误页面
在Web应用中,我们可能需要自定义错误页面。这通常与HTML模板一起使用。
示例:
func main() {
r := gin.Default()
r.LoadHTMLGlob("templates/*")
r.Use(func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
err := c.Errors.Last()
c.HTML(500, "error.tmpl", gin.H{
"error": err.Error(),
"status": 500,
})
}
})
r.GET("/error", func(c *gin.Context) {
c.Error(errors.New("something went wrong"))
})
r.Run()
}
对应的模板文件 error.tmpl:
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h1>Error: {{ .status }}</h1>
<p>{{ .error }}</p>
<a href="/">Go Home</a>
</body>
</html>
2.4. 日志记录错误
错误处理通常与日志记录相结合,以便于调试和监控:
示例:
import (
"log"
"time"
)
func LoggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
// 记录错误
if len(c.Errors) > 0 {
for _, e := range c.Errors {
log.Printf("Error: %s | Status: %d | Method: %s | Path: %s | Duration: %v",
e.Error(),
c.Writer.Status(),
c.Request.Method,
c.Request.URL.Path,
duration,
)
}
}
}
}
func main() {
r := gin.New()
r.Use(LoggingMiddleware())
r.GET("/test", func(c *gin.Context) {
c.Error(errors.New("test error"))
c.JSON(200, gin.H{"message": "Hello"})
})
r.Run()
}
3. panic与恢复
3.1. 默认 Recovery 中间件
Gin框架内置了Recovery()中间件,用于从panic中恢复,防止程序崩溃。
源码:
func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
}
Recovery方法返回一个中间件,它会捕获任意panic。如果有panic,还会应答HTTP Code 500。
示例:
func main() {
r := gin.Default() // 默认包含 Logger 和 Recovery 中间件
r.GET("/panic", func(c *gin.Context) {
panic("something went wrong!")
})
r.Run()
}
3.2. 自定义 Recovery 处理
如果需要自定义panic处理逻辑,可以使用如下的几个方法:
gin.CustomRecovery:返回Recovery处理中间件,支持自定义处理方法。gin.RecoveryWithWriter:返回Recovery处理中间件,支持自定义输出writer和处理方法,处理方法为可变参数。gin.CustomRecoveryWithWriter:返回Recovery处理中间件,支持自定义输出writer和处理方法。。
源码:
// 支持自定义处理方法
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter, handle)
}
// 支持自定义输出writer和处理方法,处理方法为可变参数。
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
if len(recovery) > 0 {
return CustomRecoveryWithWriter(out, recovery[0])
}
return CustomRecoveryWithWriter(out, defaultHandleRecovery)
}
// 支持自定义输出writer和处理方法
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
var logger *log.Logger
if out != nil {
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
}
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
var se *os.SyscallError
if errors.As(ne, &se) {
seStr := strings.ToLower(se.Error())
if strings.Contains(seStr, "broken pipe") ||
strings.Contains(seStr, "connection reset by peer") {
brokenPipe = true
}
}
}
if logger != nil {
const stackSkip = 3
if brokenPipe {
logger.Printf("%s\n%s%s", err, secureRequestDump(c.Request), reset)
} else if IsDebugging() {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
timeFormat(time.Now()), secureRequestDump(c.Request), err, stack(stackSkip), reset)
} else {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
timeFormat(time.Now()), err, stack(stackSkip), reset)
}
}
if brokenPipe {
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) //nolint: errcheck
c.Abort()
} else {
handle(c, err)
}
}
}()
c.Next()
}
}
示例:
func main() {
r := gin.New()
// 自定义 Recovery 处理函数
r.Use(gin.RecoveryWithWriter(gin.DefaultErrorWriter, func(c *gin.Context, err interface{}) {
// 自定义错误处理逻辑
c.JSON(500, gin.H{
"error": "Internal Server Error",
"message": "Something went wrong!",
})
}))
r.GET("/panic", func(c *gin.Context) {
panic("something went wrong!")
})
r.Run()
}
349

被折叠的 条评论
为什么被折叠?



