Go程序中的一般恐慌恢复

Or *_*hum 2 crash-reports go panic sentry raygun

我正在尝试从程序中创建的go例程中捕获崩溃/紧急情况,以便将其发送到崩溃错误报告服务器(例如Sentry / Raygun)

例如,

func main() {

    go func() {
        // Get this panic
        panic("Go routine panic")
    }()
}
Run Code Online (Sandbox Code Playgroud)

答案表明,一个goroutine无法从另一个goroutine的恐慌中恢复。

惯用的方式是什么?

icz*_*cza 7

您必须将一些代码“注入”到作为新的goroutine启动的函数中:您必须调用其中调用的延迟函数recover()。这是从恐慌状态中恢复的唯一方法。参见相关内容:为什么`defer recovery()`不能捕获恐慌?

例如:

go func() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Caught:", r)
        }
    }()

    panic("catch me")
}()
Run Code Online (Sandbox Code Playgroud)

这将输出(在Go Playground上尝试):

Caught: catch me
Run Code Online (Sandbox Code Playgroud)

在您启动的每个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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这将输出相同的内容(在Go Playground上尝试)。

另一个更方便,甚至更紧凑的解决方案是创建一个实用功能,即一个“包装器”,该包装器接收该功能并负责恢复。

它看起来像这样:

func wrap(f func()) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Caught:", r)
        }
    }()

    f()
}
Run Code Online (Sandbox Code Playgroud)

现在使用它更加简单:

go wrap(func() {
    panic("catch me")
})

go wrap(func() {
    panic("catch me too")
})
Run Code Online (Sandbox Code Playgroud)

它将输出(在Go Playground上尝试):

Caught: catch me
Caught: catch me too
Run Code Online (Sandbox Code Playgroud)

最后说明:

请注意,启动实际的goroutine发生在之外wrap()。这使调用者可以选择是否仅通过在wrap()调用前面加上前缀来决定是否需要新的goroutine go。通常,在Go中首选此方法。这使您可以通过将任意函数传递给来执行任意功能,wrap()即使您不希望在新的goroutine中同时运行它,也可以“保护”其执行(通过从紧急情况中恢复,正确记录/报告)。