在golang中捕获panic()

xdyibdwo  于 2023-09-28  发布在  Go
关注(0)|答案(4)|浏览(138)

我们有一个较大的golang应用程序,它使用日志记录器(实际上是一个自定义日志记录器)将输出写入定期循环的日志文件。
但是,当应用程序崩溃或panic()时,这些消息会转到标准错误。
有没有什么方法可以覆盖panic功能来使用我们的记录器?

fwzugrvs

fwzugrvs1#

据我所知,你不能将panic的输出重定向到标准错误,也不能重定向到你的logger。你能做的最好的事情就是将标准错误重定向到一个文件中,你可以在外部或程序内部这样做。
对于我的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)
    }
}

和windows

// 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
}
sc4hvdpw

sc4hvdpw2#

你可以使用recover()从同一个goroutine中恢复panics。当在延迟方法中调用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中触发的panic中恢复(感谢JimB的提示)。使用单个recover()从任何goroutine的恐慌中恢复是不可能的。

3pvhb19x

3pvhb19x3#

扩展@nick-craig-wood的回答:如果你在Linux上,你可以生成一个logger(1)示例并将stderr重定向到它。这样,您就可以将完整的回溯跟踪到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()
}
lyfkaqu1

lyfkaqu14#

更新到2023年。syscall.SysCall已被弃用,所以我正在寻找一种在Windows上捕获恐慌的替代方法。安德鲁·麦金利的回答对我很有效。下面是我用来捕获恐慌并发送到日志文件的代码

func redirectStderr(logPath string) {
    logFile, _ := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0644)
    defer logFile.Close()

    err := windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(logFile.Fd()))
    if err != nil {
        Log.Fatalf("Failed to redirect stderr to file: %v", err)
    }
    os.Stderr = logFile
}

pidoss. Rclone是一个令人敬畏的工具!

相关问题