dsnpjz6907
2016-01-13 16:32 浏览 199
已采纳

在golang中捕获panic()

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?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

3条回答 默认 最新

  • 已采纳
    dongxiaoshe0737 dongxiaoshe0737 2016-01-13 18:06

    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
    }
    
    点赞 评论 复制链接分享
  • dongzhan1383 dongzhan1383 2016-01-13 16:55

    You can use recover() to recover panics from the same goroutine. When calling recover() in a deferred method (remember that deferred methods will still be called, even when panic()ing), it will return whatever was passed into the last panic() call as argument (or nil when the program is not panicking).

    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");
    

    Note however, that you cannot recover from panics that were triggered in a different goroutine (thanks to JimB for the hint). Using a single recover() to recover from panics from any goroutine is not possible.

    点赞 评论 复制链接分享
  • doudou5023 doudou5023 2017-05-24 18:10

    Expanding @nick-craig-wood's answer: If you are on Linux you can spawn a logger(1) instance and redirect stderr to it. That way you get the full backtrace into syslog. This is what gocryptfs does:

    // 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
    ", 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
    ", err)
        }
        pr.Close()
        err = syscall.Dup2(int(pw.Fd()), 1)
        if err != nil {
            tlog.Warn.Printf("redirectStdFds: stdout dup error: %v
    ", err)
        }
        syscall.Dup2(int(pw.Fd()), 2)
        if err != nil {
            tlog.Warn.Printf("redirectStdFds: stderr dup error: %v
    ", err)
        }
        pw.Close()
    
        // stdin
        nullFd, err := os.Open("/dev/null")
        if err != nil {
            tlog.Warn.Printf("redirectStdFds: could not open /dev/null: %v
    ", err)
            return
        }
        err = syscall.Dup2(int(nullFd.Fd()), 0)
        if err != nil {
            tlog.Warn.Printf("redirectStdFds: stdin dup error: %v
    ", err)
        }
        nullFd.Close()
    }
    
    点赞 评论 复制链接分享

相关推荐