Go 语言 panic recover 实战指南
目录
一、关于 panic
panic 俗称 恐慌
panic
是 Go 语言中用于处理程序不可恢复错误的机制。当程序遇到无法继续执行的严重错误时,可以触发 panic
。
-
- 不可恢复的错误:如数组越界、空指针解引用等
-
- 程序逻辑错误:如不应该发生的代码被执行、必要的配置文件缺失,比如标准库中 regexp.MustCompile() 正则表达式编写错误直接就 panic
一旦触发 panic ,程序会直接不可用
当然这不是我们想要的结果,谁也不想出现错误就导致整个程序不可用,那么就可以用 recover 对 panic 进行捕获处理
二、reocver 的使用
(一)原理
[panic触发]
│
▼
┌─────────────────────┐
│ 逆向执行当前函数内的defer栈 │
└─────────────────────┘
│
仅在defer函数内部的recover生效
(二)代码案例
func main(){
res,err:=SafeDivide(1,0)
if err!=nil{
fmt.Println(err)
}
// 其他代码
fmt.Println("hello world") // 如果 SafeDivide 进行recover,该代码才会执行,反之不执行,程序直接关闭
}
func SafeDivide(a, b int) (res int, err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("panic: %v", e)
}
}()
return a / b, nil // 可能触发除零panic
}
如果没有进行 recover,主程序调用 SafeDivide 函数之后,程序之后的逻辑将不会执行,程序直接关闭
进行 recover,SafeDivide 函数即便发生 panic,也不会影响主程序后续其他代码的执行
(三)同时打印堆栈
recover 之后同时打印堆栈日志,这在生产环境排查问题将非常有用
func SafeDivide(a, b int) (res int, err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("panic: %v", e)
log.Errorln("recover success.")
buf := make([]byte, 1<<16)
runtime.Stack(buf, true)
log.Errorf(" recover %s", string(buf))
}
}()
return a / b, nil // 可能触发除零panic
}
(四)gin 框架集成
通过中间件集成到 gin 框架,确保 web 程序不会出现 panic
package main
import (
"fmt"
"io"
"net/http"
"runtime"
"time"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
)
func main() {
r := gin.Default()
r.Use(gin.Recovery()) // 使用默认的 recover
r.Use(gin.CustomRecoveryWithWriter(io.Discard, func(c *gin.Context, r any) { // io.Discard 表示不输出到控制台,然后自定义 handler,集成到自己的 logs 日志
err, ok := r.(error)
if ok {
buf := make([]byte, 1<<16)
runtime.Stack(buf, false)
err = fmt.Errorf("[Recovery] %s panic recovered:\n%s\n%s",
time.Now(), r, buf)
err = errors.WithStack(err)
_ = c.Error(err)
} else {
err = errors.New(fmt.Sprintf("%v", r))
}
logs.Errorf("recover:%v", err) // 将 recover 日志输出到自己的 logs 里
c.AbortWithStatus(http.StatusInternalServerError)
}))
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
注意事项
-
panic 会不断向上冒泡,直到主程序,中间遇到 recover 则结束,若直到主程序都没有recover,则程序直接关闭
比如 main -> A() -> B() -> C() 当函数 C() 发生 panic,会一直向上找 recover
-
panic 只能被当前的协程捕获,父协程也无法捕获
func main(){ defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic: %v", e) log.Errorln("main recover success.") // 不会执行,无法捕获子协程的 panic,程序直接关闭 } }() go Divide(1,0) // 子协程发生 panic,只能被该协程自己 recover,父协程无法捕获 select{} }
func Divide(a, b int) (res int, err error) { // defer func() { // if e := recover(); e != nil { // err = fmt.Errorf(“panic: %v”, e) // log.Errorln(“Divide recover success.”) //
// } // }()return a / b, nil // 可能触发除零panic
}