3

我试图从我的程序中创建的 go 例程中捕获崩溃/恐慌,以便将它们发送到我的崩溃错误报告服务器(例如 Sentry/Raygun)

例如,

func main() {

    go func() {
        // Get this panic
        panic("Go routine panic")
    }()
}

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

惯用的方法是什么?

4

1 回答 1

8

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

例如:

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

    panic("catch me")
}()

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

Caught: catch me

在您启动的每个 goroutine 中执行此操作是不可行的,但当然您可以将恢复日志记录功能移动到命名函数,然后调用它(但当然推迟):

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(). 这让调用者可以选择是否需要一个新的 goroutine,只需在wrap()调用前加上go. 通常这种方法在 Go 中是首选的。这允许您通过将任意函数传递给 来执行任意函数wrap(),并且即使您不希望在新的 goroutine 中同时运行它,它也会“保护”其执行(通过从恐慌中恢复、正确记录/报告它)。

于 2019-05-21T09:21:53.877 回答