logginggopanic

Capturing panic() in golang


We have a large-ish golang application that uses the logger (actually, a custom logger), to write output to a log file that is periodically rotated.

However, when an application crashes or panic()'s, those messages go to standard error.

Is there any way to override the panic functionality to use our logger?


Solution

  • As far as I know, you can't redirect the output from panic away from standard error, or to your logger. The best thing you can do is redirect standard error to a file which you can do externally, or inside your program.

    For my rclone program I redirected standard error to capture everything to a file on an option which is unfortunately isn't particularly easy to do in a cross platform way. Here is how I did it (see the redirect*.go files)

    For 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)
        }
    }
    

    and for 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
    }