如何获取 panic 的堆栈跟踪信息(并将其存储为变量)

78

众所周知,当出现panic时,会在标准输出中产生一个堆栈跟踪信息(Playground链接)。

panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
    /tmp/sandbox579134920/main.go:9 +0x20

看起来当你从惊恐中恢复过来时,recover()只返回一个描述引起惊恐的错误(演示链接)。

runtime error: index out of range

我的问题是,是否可以将写入标准输出的堆栈跟踪存储起来?这提供了比字符串runtime error: index out of range更好的调试信息,因为它显示了导致恐慌的文件中的确切行。


2
你应该阅读这篇文章,并查看这个链接 - user4466350
3
使用调试包(debug package)。 - Volker
我认为一定有解决方法。例如,我一直在使用runtime/debug来捕获堆栈跟踪,可以参考这里。不过我很好奇其他贡献者是否有更好的解决方案。 - hlin117
3个回答

110

就像@Volker上面提到的,并且作为评论发布的,我们可以使用runtime/debug包。

package main

import (
    "fmt"
    "runtime/debug"
)

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
        }
    }()

    var mySlice []int
    j := mySlice[0]

    fmt.Printf("Hello, playground %d", j)
}

打印

stacktrace from panic: 
goroutine 1 [running]:
runtime/debug.Stack(0x1042ff18, 0x98b2, 0xf0ba0, 0x17d048)
    /usr/local/go/src/runtime/debug/stack.go:24 +0xc0
main.main.func1()
    /tmp/sandbox973508195/main.go:11 +0x60
panic(0xf0ba0, 0x17d048)
    /usr/local/go/src/runtime/panic.go:502 +0x2c0
main.main()
    /tmp/sandbox973508195/main.go:16 +0x60

游乐场链接.


25
为了明确(因为这个案例没有明确测试这一点): 当你在recover()函数内部时,debug.Stack()函数将返回PANIC的堆栈,而不仅仅是延迟函数的词法堆栈。 - Jon Watte
@JonWatte 此外,原始的 panic 位置将会在堆栈跟踪中更靠上。 - Pavel Patrin
这个在调试包中的代码是否可以安全地用于生产环境? - DubDub
更好的例子:https://go.dev/play/p/tPsHL23E2rZ - undefined

9
创建日志文件,将堆栈跟踪添加到标准输出或标准错误的文件中。这将在文件中添加包括错误时间和行号在内的数据。
package main

import (
    "log"
    "os"
    "runtime/debug"
)

func main() {

    defer func() {
        if r := recover(); r != nil {
            log.Println(string(debug.Stack()))
        }
    }()

    //create your file with desired read/write permissions
    f, err := os.OpenFile("filename", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
    if err != nil {
        log.Println(err)
    }

    //set output of logs to f
    log.SetOutput(f)
    var mySlice []int
    j := mySlice[0]

    log.Println("Hello, playground %d", j)

    //defer to close when you're done with it, not because you think it's idiomatic!
    f.Close()
}

Go playground上的实际工作示例。


-1

这里提供一种解决方案,将 panic 转换为带有堆栈跟踪的错误,并像其他错误一样记录

package main

import (
    "fmt"

    "github.com/pkg/errors"
)

func wrong() int {
    var mySlice []int
    return mySlice[0]
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("Error: %+v", errors.New(fmt.Sprintf("%v", r)))
        }
    }()

    fmt.Printf("Hello, playground %d", wrong())
}

Go Playground 输出

Error: runtime error: index out of range [0] with length 0
main.main.func1
    /tmp/sandbox2058999152/prog.go:17
runtime.gopanic
    /usr/local/go-faketime/src/runtime/panic.go:884
runtime.goPanicIndex
    /usr/local/go-faketime/src/runtime/panic.go:113
main.wrong
    /tmp/sandbox2058999152/prog.go:11
main.main
    /tmp/sandbox2058999152/prog.go:21
runtime.main
    /usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
    /usr/local/go-faketime/src/runtime/asm_amd64.s:1594
Program exited.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接