我们有一个大型的golang应用程序,它使用记录器(实际上是自定义记录器)将输出写入定期轮换的日志文件中。

但是,当应用程序崩溃或出现panic()时,这些消息将变为标准错误。

有什么方法可以覆盖使用记录仪的紧急功能?

  • 我认为recover比重定向输出更好。 debug.PrintStack可以打印您需要的日志。 其他一些日志(例如GC消息)仍可以保留在stderr中。

据我所知,您无法将输出从紧急状态重定向到标准错误或记录器。最好的办法是将标准错误重定向到可以在外部或在程序内部执行的文件。

对于我的rclone程序,我重定向了标准错误,以将所有内容捕获到一个选项上的文件中,但是不幸的是,以跨平台的方式并不是一件容易的事。这是我的操作方法(请参阅redirect * .go文件)

对于linux / unix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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)
    }
}

和窗户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 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
}
  • 这对我有用-恐慌发给我的fd而不是stderr解决了我的问题。
  • 我尝试了这一点,Unix方面对我有用,但Windows方面没有...任何见识? -play.golang.org/p/jQaN1bNypx
  • 我做了更多实验,在Windows上也必须执行os.Stderr = f,因为SetStdHandle不会影响对stderr的先前引用。在UNIX上,这是没有必要的,因为Dup2确实会影响对stderr的先前引用。 play.golang.org/p/mXjw6lOrG9
  • 请参阅下面的雅各布斯答案。如果您使用Go支持的POSIX或BSD格式,则可以使用。我目前正在与他合作,看看我们是否可以清理掉其中的一些结果,以便使gocryptfs的日志记录更加一致。
  • 有没有一种方法可以将整个stderr重定向到Windows中的文件?我正在尝试对所有消息进行此操作,但是这只会使文件出现恐慌。我能够重定向所有STDOUT,但不能重定向错误。 @TimLewis

您可以使用recover()从同一goroutine中恢复恐慌。在延迟方法中调用recover()时(请记住,即使在panic() ing时,延迟方法仍将被调用),它将返回上一次panic()调用中传递的内容作为参数(或在程序时为nil不惊慌)。

1
2
3
4
5
6
7
8
9
10
11
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中从恐慌中恢复。

  • 我会指出,您只能从自己的goroutine中恢复。我认为OP可能正在寻找无法做到的全局"恢复"。
  • 的确如此。麻烦编辑,谢谢提示。
  • 好吧,我不需要停止恐慌,我只希望回溯出现在日志中,而不是stderr。我想我可以用记录器替换os.Stderr吗?没错-刚刚检查过,即使使用os.Stderr作为我的日志文件句柄,也仍然会惊慌于stderr。
  • 请记住,即使恢复,调用panic(x)也会杀死您的进程。如果只需要堆栈,请调用debug.PrintStack()
  • 那就是我想要的

扩展@ nick-craig-wood的答案:
如果您使用的是Linux,则可以生成logger(1)实例并将stderr重定向到该实例。这样,您就可以将完整的回溯信息记录到syslog中。这是gocryptfs的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 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()
}