Panic 是 Go 语言中不可忽视的问题,如果不处理,整个服务会崩溃。Recovery 中间件可以捕获 panic,保证服务继续运行。
gin.Default() 默认包含 Recovery 中间件:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/panic", func(c *gin.Context) {
panic("出错了!")
})
r.Run(":8080")
}
访问 /panic 时,服务不会崩溃,而是返回 500 错误,日志会记录 panic 信息。
如果使用 gin.New(),需要手动添加:
func main() {
r := gin.New()
r.Use(gin.Recovery())
r.GET("/hello", func(c *gin.Context) {
c.String(200, "Hello")
})
r.Run(":8080")
}
自定义 panic 处理逻辑:
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recovered: %v\n%s", err, debug.Stack())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "服务器内部错误",
})
}
}()
c.Next()
}
}
r.Use(Recovery())
func DetailedRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
requestID, _ := c.Get("requestID")
logEntry := fmt.Sprintf(
"[PANIC] RequestID: %v, Path: %s, Method: %s, Error: %v\nStack:\n%s",
requestID,
c.Request.URL.Path,
c.Request.Method,
err,
debug.Stack(),
)
log.Println(logEntry)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "服务器内部错误",
"requestId": requestID,
})
}
}()
c.Next()
}
}
r.Use(RequestID())
r.Use(DetailedRecovery())
func TypedRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
var statusCode int
var message string
switch e := err.(type) {
case *CustomError:
statusCode = e.Code
message = e.Message
case error:
statusCode = http.StatusInternalServerError
message = "服务器内部错误"
log.Printf("Panic: %v\n%s", e, debug.Stack())
default:
statusCode = http.StatusInternalServerError
message = "未知错误"
log.Printf("Panic: %v\n%s", err, debug.Stack())
}
c.AbortWithStatusJSON(statusCode, gin.H{
"code": statusCode,
"message": message,
})
}
}()
c.Next()
}
}
type CustomError struct {
Code int
Message string
}
func (e *CustomError) Error() string {
return e.Message
}
r.GET("/custom-error", func(c *gin.Context) {
panic(&CustomError{Code: 400, Message: "自定义错误"})
})
发生 panic 时发送通知:
func NotifyRecovery(notify func(string)) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
stack := debug.Stack()
msg := fmt.Sprintf(
"[PANIC] Time: %s, Path: %s, Error: %v\n%s",
time.Now().Format(time.RFC3339),
c.Request.URL.Path,
err,
stack,
)
log.Println(msg)
notify(msg)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "服务器内部错误",
})
}
}()
c.Next()
}
}
func sendAlert(msg string) {
}
r.Use(NotifyRecovery(sendAlert))
Recovery 和 Logger 配合使用:
func main() {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.GET("/test", func(c *gin.Context) {
if c.Query("error") == "true" {
panic("测试错误")
}
c.String(200, "OK")
})
r.Run(":8080")
}
func ProductionRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
requestID := c.GetString("requestID")
log.Printf(
"[RECOVERY] RequestID=%s, Path=%s, Error=%v",
requestID,
c.Request.URL.Path,
err,
)
if gin.Mode() == gin.DebugMode {
log.Printf("Stack:\n%s", debug.Stack())
}
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "Internal Server Error",
"requestId": requestID,
})
}
}()
c.Next()
}
}
Recovery 中间件是生产环境必备的,它能防止 panic 导致服务崩溃。建议配合日志记录和错误通知使用,方便排查问题。记得在开发环境打印完整的堆栈信息,生产环境可以精简。