Захват паники() в голанге
У нас есть приложение с большим количеством ish golang, которое использует регистратор (фактически, собственный регистратор) для записи вывода в файл журнала, который периодически поворачивается.
Однако при сбое приложения или панике() эти сообщения переходят к стандартной ошибке.
Есть ли способ переопределить функцию паники для использования нашего регистратора?
Ответы
Ответ 1
Насколько я знаю, вы не можете перенаправлять вывод из паники в сторону от стандартной ошибки или в ваш регистратор. Лучшее, что вы можете сделать, это перенаправить стандартную ошибку в файл, который вы можете делать извне или внутри вашей программы.
Для моей программы rclone я перенаправил стандартную ошибку, чтобы захватить все в файл по опции, которая, к сожалению, не очень проста сделать кросс-платформенным способом. Вот как я это сделал (см. Файлы перенаправления *.go)
Для linux/unix
// Log the panic under unix to the log file
//+build unix
package main
import (
"log"
"os"
"syscall"
)
// redirectStderr to the file passed in
func redirectStderr(f *os.File) {
err := syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd()))
if err != nil {
log.Fatalf("Failed to redirect stderr to file: %v", err)
}
}
и для окон
// Log the panic under windows to the log file
//
// Code from minix, via
//
// http://play.golang.org/p/kLtct7lSUg
//+build windows
package main
import (
"log"
"os"
"syscall"
)
var (
kernel32 = syscall.MustLoadDLL("kernel32.dll")
procSetStdHandle = kernel32.MustFindProc("SetStdHandle")
)
func setStdHandle(stdhandle int32, handle syscall.Handle) error {
r0, _, e1 := syscall.Syscall(procSetStdHandle.Addr(), 2, uintptr(stdhandle), uintptr(handle), 0)
if r0 == 0 {
if e1 != 0 {
return error(e1)
}
return syscall.EINVAL
}
return nil
}
// redirectStderr to the file passed in
func redirectStderr(f *os.File) {
err := setStdHandle(syscall.STD_ERROR_HANDLE, syscall.Handle(f.Fd()))
if err != nil {
log.Fatalf("Failed to redirect stderr to file: %v", err)
}
// SetStdHandle does not affect prior references to stderr
os.Stderr = f
}
Ответ 2
Вы можете использовать recover()
для восстановления паники из той же самой goroutine. При вызове recover()
в отложенном методе (помните, что отложенные методы все равно будут вызываться, даже если panic()
ing), он вернет все, что было передано в последний вызов panic()
в качестве аргумента (или nil
, когда программа не паникует).
defer func() {
if x := recover(); x != nil {
// recovering from a panic; x contains whatever was passed to panic()
log.Printf("run time panic: %v", x)
// if you just want to log the panic, panic again
panic(x)
}
}()
panic("foo");
Обратите внимание, однако, что вы не можете восстановить из-за паники, которые были запущены в другом goroutine (спасибо JimB за подсказку). Использование одиночного recover()
для восстановления после паники с любого горутинга невозможно.
Ответ 3
Расширение ответа @nick-craig-wood:
Если вы находитесь в Linux, вы можете создать экземпляр регистратора (1) и перенаправить stderr на него. Таким образом, вы получите полный backtrace в syslog. Это то, что gocryptfs делает:
// redirectStdFds redirects stderr and stdout to syslog; stdin to /dev/null
func redirectStdFds() {
// stderr and stdout
pr, pw, err := os.Pipe()
if err != nil {
tlog.Warn.Printf("redirectStdFds: could not create pipe: %v\n", err)
return
}
tag := fmt.Sprintf("gocryptfs-%d-logger", os.Getpid())
cmd := exec.Command("logger", "-t", tag)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = pr
err = cmd.Start()
if err != nil {
tlog.Warn.Printf("redirectStdFds: could not start logger: %v\n", err)
}
pr.Close()
err = syscall.Dup2(int(pw.Fd()), 1)
if err != nil {
tlog.Warn.Printf("redirectStdFds: stdout dup error: %v\n", err)
}
syscall.Dup2(int(pw.Fd()), 2)
if err != nil {
tlog.Warn.Printf("redirectStdFds: stderr dup error: %v\n", err)
}
pw.Close()
// stdin
nullFd, err := os.Open("/dev/null")
if err != nil {
tlog.Warn.Printf("redirectStdFds: could not open /dev/null: %v\n", err)
return
}
err = syscall.Dup2(int(nullFd.Fd()), 0)
if err != nil {
tlog.Warn.Printf("redirectStdFds: stdin dup error: %v\n", err)
}
nullFd.Close()
}