在 Golang 中 recover 函数是用来恢复一个发生panic 的 goroutine 避免整个程序 crash 。基本上 recover 只能和 defer 函数结合使用,因为一旦发生 panic 正常的执行流程会被打断,只有 defer 函数还能正常工作。我们业务中通常会对一些通用的逻辑做一些重复的封装,recover 的逻辑也不例外。最近发现线上一些协程的任务会 panic 导致程序 crash ,下面是我们的封装函数 CatchRecover 封装了一个统一处理 recover 返回值逻辑的函数。
func CatchRecover(r interface{}) {
if r != nil {
// 原生logger
var logger *log.Logger
logger = log.New(os.Stderr, "\n\n\x1b[31m", log.LstdFlags)
// logger
fields := make(map[string]interface{})
fields["CLASS"] = enums.PANICLoggerClass.String()
stdLogger := utils.NewEmptyLoggerWithFields(fields).AddDefaultHook()
stack := stack.Stack(4)
msg := fmt.Sprintf("[QVM Recovery] panic recovered:\n%s\n%s%s", r, stack, reset)
logger.Println(msg)
stdLogger.Error(msg)
}
}
Go
在 CatchRecover 中我们会对传入的 r 值做判断,然后打印出 panic 的函数栈信息。
在 defer 函数中调用 CatchRecover 函数的方式有下面两种方式
1:
defer func() {
CatchRecover(recover())
}()
Go
2:
defer CatchRecover(recover())
Go
结果发现:
第一种方式能正常的 recover panic
第二种程序还是会 panic 并没有 recover 成功
对上面的代码进行仔细的分析不难发现
第一种方式是一个函数闭包的方式将执行体压入 defer 栈中
第二种方式是将函数 CatchRecover 压入 defer 栈中,这个过程中函数的参数 recover 函数会在这个时候执行,而不是在 defer function 中执行的,此时程序运行正常 recover 到的值是 nil ,而在程序真正发生 panic 的时候 recover 函数已经执行过了,所以不能阻止程序退出
第二种方式在实际编码的过程中还挺难发现的,而且如果存在会对程序产生不可预期的行为。