Родовая паника восстанавливается в го программах - PullRequest
2 голосов
/ 21 мая 2019

Я пытаюсь поймать сбои / паники из подпрограмм go, созданных в моей программе, чтобы отправить их на мой сервер отчетов об ошибках (например, Sentry / Raygun)

Например,

func main() {

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

В ответе говорится, что программа не может оправиться от паники в другой программе.

Каким будет идиоматический способ сделать это?

1 Ответ

6 голосов
/ 21 мая 2019

Вы должны «внедрить» некоторый код в функцию, которая запускается как новая процедура: вам нужно вызвать отложенную функцию, в которой вы вызываете recover().Это единственный способ оправиться от состояния паники.См. Связанный: Почему `defer recovery ()` не улавливает панику?

Например:

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

    panic("catch me")
}()

Это выдаст (попробуйте на Go Playground ):

Caught: catch me

Невозможно делать это на каждой запускаемой вами подпрограмме, но, конечно, вы можете переместить функцию восстановления-записи в именованную функцию и просто вызвать ее (ноконечно, отложено):

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

Заключительная нота:

Обратите внимание, что запуск реальной программы происходит за пределами wrap().Это дает вызывающей стороне возможность решить, требуется ли новая процедура, просто добавив префикс wrap() к go.Обычно такой подход предпочтительнее в Go.Это позволяет вам выполнять произвольные функции, передавая их wrap(), и он будет "защищать" свое выполнение (восстанавливаясь от паники, правильно регистрируя / сообщая об этом), даже если вы не хотите запускать его одновременно в новой программе.

...