我正试图从程序中创建的go例程中捕获崩溃/恐慌,以便将其发送至崩溃报告错误的服务器(例如Sentry / Raygun)
例如,
func main() {
go func() {
// Get this panic
panic("Go routine panic")
}()
}
The answer指出一个goroutine无法从另一个goroutine的恐慌中恢复。
惯用的方式是什么?
答案 0 :(得分:6)
您必须将一些代码“注入”作为新的goroutine启动的函数中:您必须调用一个延迟函数,在其中调用recover()
。这是从恐慌状态中恢复的唯一方法。查看相关内容:Why does `defer recover()` not catch panics?
例如:
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
panic("catch me")
}()
这将输出(在Go Playground上尝试):
Caught: catch me
在您启动的每个goroutine中执行此操作都是不可行的,但是您当然可以将recovery-logging功能移到一个命名函数,然后调用它(当然要推迟):
func main() {
go func() {
defer logger()
panic("catch me")
}()
time.Sleep(time.Second)
}
func logger() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}
这将输出相同的内容(在Go Playground上尝试)。
另一个更方便,更紧凑的解决方案是创建一个实用函数,即一个“包装器”,该包装器接收该功能并负责恢复。
它是这样的:
func wrap(f func()) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught:", r)
}
}()
f()
}
现在使用它更加简单:
go wrap(func() {
panic("catch me")
})
go wrap(func() {
panic("catch me too")
})
它将输出(在Go Playground上尝试):
Caught: catch me
Caught: catch me too
最终备注:
请注意,启动实际的goroutine发生在wrap()
之外。这使调用者可以选择是否仅通过在wrap()
前面加上go
来决定是否需要新的goroutine。通常,在Go中首选此方法。这样,您可以通过将任意函数传递给wrap()
来执行任意功能,即使您不希望在新的程序中同时运行它,也可以“保护”其执行(通过从紧急情况中恢复,正确记录/报告)。 goroutine。