为了账号安全,请及时绑定邮箱和手机立即绑定

在 golang 中捕获 panic()

在 golang 中捕获 panic()

Go
尚方宝剑之说 2021-12-07 10:20:28
我们有一个大型 golang 应用程序,它使用记录器(实际上是一个自定义记录器)将输出写入定期轮换的日志文件。但是,当应用程序崩溃或 panic() 时,这些消息会转为标准错误。有没有办法覆盖恐慌功能以使用我们的记录器?
查看完整描述

3 回答

?
冉冉说

TA贡献1877条经验 获得超1个赞

据我所知,您无法将 panic 的输出重定向到远离标准错误或您的记录器。您能做的最好的事情是将标准错误重定向到您可以在外部或程序内部执行的文件。


对于我的rclone程序,我重定向了标准错误以将所有内容捕获到一个选项的文件中,不幸的是,这在跨平台方式中并不是特别容易做到。这是我是如何做到的(请参阅 redirect*.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

}


查看完整回答
反对 回复 2021-12-07
?
慕码人2483693

TA贡献1860条经验 获得超9个赞

您可以使用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()从任何 goroutine 的恐慌中恢复是不可能的。


查看完整回答
反对 回复 2021-12-07
?
慕森卡

TA贡献1806条经验 获得超8个赞

扩展@nick-craig-wood 的回答:如果您使用的是 Linux,您可以生成一个 logger(1) 实例并将 stderr 重定向到它。这样你就可以在系统日志中获得完整的回溯。这就是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()

}


查看完整回答
反对 回复 2021-12-07
  • 3 回答
  • 0 关注
  • 252 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信