Panic 会导致程序崩溃,在 Web 服务中必须妥善处理。Gin 的 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 错误。
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 := gin.New()
r.Use(Recovery())
type PanicError struct {
Code int
Message string
}
func (e *PanicError) Error() string {
return e.Message
func TypedRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
var code int
var message string
switch e := err.(type) {
case *PanicError:
code = e.Code
message = e.Message
case error:
code = http.StatusInternalServerError
message = "服务器内部错误"
log.Printf("Panic: %v\n%s", e, debug.Stack())
case string:
code = http.StatusInternalServerError
message = "服务器内部错误"
log.Printf("Panic: %s\n%s", e, debug.Stack())
default:
code = http.StatusInternalServerError
message = "未知错误"
log.Printf("Panic: %v\n%s", err, debug.Stack())
}
c.AbortWithStatusJSON(code, gin.H{
"code": code,
"message": message,
})
}
}()
c.Next()
}
}
r.GET("/business-panic", func(c *gin.Context) {
panic(&PanicError{Code: 400, Message: "业务错误"})
})
func NotifyRecovery(notify func(string)) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
requestID, _ := c.Get("requestID")
msg := fmt.Sprintf(
"[PANIC] RequestID: %v, Path: %s, Error: %v\n%s",
requestID,
c.Request.URL.Path,
err,
debug.Stack(),
)
log.Println(msg)
notify(msg)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "服务器内部错误",
})
}
}()
c.Next()
}
}
func sendAlert(msg string) {
fmt.Println("发送告警:", msg)
}
r.Use(NotifyRecovery(sendAlert))
某些场景下可以主动 panic:
func MustGetUser(c *gin.Context) *User {
user, exists := c.Get("currentUser")
if !exists {
panic(&PanicError{Code: 401, Message: "请先登录"})
}
return user.(*User)
}
r.GET("/profile", func(c *gin.Context) {
user := MustGetUser(c)
c.JSON(200, user)
})
Panic 后 Context 仍然可用:
r.GET("/panic-context", func(c *gin.Context) {
c.Set("before_panic", "panic 前设置的值")
panic("测试 panic")
})
r.Use(func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
val, _ := c.Get("before_panic")
log.Printf("Panic: %v, Context value: %v", err, val)
c.AbortWithStatusJSON(500, gin.H{"error": "内部错误"})
}
}()
c.Next()
})
func EnvRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
requestID, _ := c.Get("requestID")
if gin.Mode() == gin.DebugMode {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": fmt.Sprintf("%v", err),
"requestId": requestID,
"stack": string(debug.Stack()),
})
} else {
log.Printf("[PANIC] %v\n%s", err, debug.Stack())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "服务器内部错误",
"requestId": requestID,
})
}
}
}()
c.Next()
}
}
func CombinedRecovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
c.Error(fmt.Errorf("panic: %v", err)).
SetType(gin.ErrorTypePrivate).
SetMeta(gin.H{"stack": string(debug.Stack())})
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "服务器内部错误",
})
}
}()
c.Next()
}
}
Recovery 中间件是生产环境必备的。自定义 Recovery 可以实现更精细的错误处理,比如区分错误类型、发送告警通知。开发环境可以返回详细错误信息,生产环境则隐藏敏感信息。记住,Panic 应该只用于真正的异常情况,不要滥用。