如何让堆栈跟踪指向Golang中的实际错误原因?

时间:2015-10-09 09:07:37

标签: go

假设我有一些像这样的代码:

value, err := some3rdpartylib.DoSomething()
if err != nil {
    panic(err)
}

如果err != nil我会得到这样的结果:

panic: some error explanation here

goroutine 1 [running]:
main.main()
    /tmp/blabla/main.go:6 +0x80

这个堆栈跟踪是完全合法的,但有时这些错误消息可能无法澄清发生了什么,因此我想深入研究第三方库的源代码,以调查究竟是什么原因导致返回此错误。但是,当我的代码像这样恐慌时,无法获得返回此错误的实际位置。

稍微澄清一点:因为我来自JVM世界,在那里引发了异常,我可以完全跟踪究竟是什么行代码抛出异常,从而可以轻松找到该位置并查看出错的地方。 Go堆栈跟踪正好在我的代码恐慌的地方结束,因此在我的情况下不太有用。

我创建了一个游乐场here,理想情况下,我希望能够将错误追踪到实际返回的地方,而不是恐慌。 (例如,到第17行,return "", errors.New("some error explanation here")

这甚至可能吗?

7 个答案:

答案 0 :(得分:5)

很快:这是不可能的。 自errors are values起,他们不会以任何特殊方式对待。因此,当函数(正常)返回时,堆栈不再可用(即,另一个函数调用可能会覆盖返回错误函数'堆栈所使用的内存)。

有一个名为 trace 的工具,它是随go1.5引入的,但是目前还没有全面的教程可用,我找到的任何一个都没有说会包含这种功能。

答案 1 :(得分:1)

看看:https://github.com/efimovalex/stackerr

这是你要找的东西吗?

package main

import "github.com/efimovalex/stackerr"
import "fmt"

func f1() *stackerr.Err {
    err := stackerr.Error("message")
    return err.Stack()
}

func f2() *stackerr.Err {
    err := f1()
    return err.Stack()
}

type t1 struct{}

func (t *t1) f3() *stackerr.Err {
    err := f2()
    return err.Stack()
}

func main() {
    ts := t1{}
    err := ts.f3()

    err.Log()
}

结果:

2017/08/31 12:13:47 Error Stacktrace:
-> github.com/efimovalex/stackerr/example/main.go:25 (main.main)
-> github.com/efimovalex/stackerr/example/main.go:19 (main.(*t1).f3)
-> github.com/efimovalex/stackerr/example/main.go:12 (main.f2)
-> github.com/efimovalex/stackerr/example/main.go:7 (main.f1)

答案 2 :(得分:1)

正如其他人所指出的那样,追踪错误并非易事。有一些项目,如jujo/errors,允许您包装错误,然后追溯这些错误。为了使这项工作变得艰难,您必须在整个项目中始终如一地使用它们,这对第3方库中的错误或处理错误并且永远不会被返回的错误无效。

因为这是一个很常见的问题而且我对此非常恼火。我写了一个小debug utility,它将添加调试代码来记录每个返回错误的文件(实现error的值)以及将其返回到STDOUT的函数(如果您需要更高级的日志记录)破解项目中的记录器,这非常简单。)

<强>安装

go get github.com/gellweiler/errgotrace
go install github.com/gellweiler/errgotrace

<强>用法

调试当前目录中的所有文件:

$ find . -name '*.go' -print0 | xargs -0 errgotrace -w

从go文件中删除添加的调试代码:

$ find . -name '*.go' -print0 | xargs -0 errgotrace -w -r

然后只需简单编译&amp;运行您的代码或测试用例。

示例输出

[...]
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectKey: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectItem: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectKey: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectItem: EOF token found
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectKey: At 3:4: nested object expected: LBRACE got: ASSIGN
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectItem: At 3:4: nested object expected: LBRACE got: ASSIGN
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.objectList: At 3:4: nested object expected: LBRACE got: ASSIGN
2017/12/13 00:54:39 [ERRGOTRACE] parser.*Parser.Parse: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] parser.Parse: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] hcl.parse: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] hcl.ParseBytes: At 2:31: literal not terminated
2017/12/13 00:54:39 [ERRGOTRACE] formula.parse: parsing failed
[...]

从这个输出中可以看出,很容易判断错误最初发生在哪个函数中。一旦知道了,就可以使用调试器来获取更多上下文。

答案 3 :(得分:1)

看看https://github.com/ztrue/tracerr

我创建此程序包的目的是为了使堆栈跟踪和源代码片段都能更快地调试并记录错误,并提供更多详细信息。

这是一个代码示例:

package main

import (
    "io/ioutil"
    "github.com/ztrue/tracerr"
)

func main() {
    if err := read(); err != nil {
        tracerr.PrintSourceColor(err)
    }
}

func read() error {
    return readNonExistent()
}

func readNonExistent() error {
    _, err := ioutil.ReadFile("/tmp/non_existent_file")
    // Add stack trace to existing error, no matter if it's nil.
    return tracerr.Wrap(err)
}

这是输出: golang error stack trace

答案 4 :(得分:1)

package main

import (
    "errors"
    "fmt"
)

func main() {
    value, err := DoSomething()
    if err != nil {
        panic(err)
    }
    fmt.Println(value)
}

func DoSomething() (string, error) {
    return "", errors.New("some error explanation here")
}

问题在于标准软件包errors在发生时没有附加堆栈跟踪。您可以使用github.com/pkg/errors来做到这一点:

package main

import (
    "github.com/pkg/errors"
    "fmt"
)

func main() {
    value, err := DoSomething()
    if err != nil {
        fmt.Printf("%+v", err)
    }
    fmt.Println(value)
}

func DoSomething() (string, error) {
    return "", errors.New("some error explanation here")
}
$ go run stacktrace.go
some error explanation here
main.DoSomething
    /Users/quanta/go/src/github.com/quantonganh/errors/stacktrace.go:18
main.main
    /Users/quanta/go/src/github.com/quantonganh/errors/stacktrace.go:10
runtime.main
    /usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204
runtime.goexit
    /usr/local/Cellar/go/1.15.2/libexec/src/runtime/asm_amd64.s:1374

更多详细信息:https://dave.cheney.net/2016/06/12/stack-traces-and-the-errors-package

答案 5 :(得分:0)

据我所知,stackrerror是最简单的堆栈显示包。您可以使用所有本机日志库记录,也可以自己输出调用堆栈。例如:

    package main

    import "github.com/lingdor/stackerror"

    func act1()error {
    return stackerror.New("here Error")
    }

    func main(){
    err:=act1()
    fmt.println(err.Error()) //panic(err) and log.Info(err) are ok
    }

输出:

*stackError.stackError : here Error
at main.act1( /Users/user/go/testMain/src/main/main.go:17 )
at main.main( /Users/user/go/testMain/src/main/main.go:22 )
at runtime.main( /usr/local/Cellar/go/1.13.4/libexec/src/runtime/proc.go:203 )

答案 6 :(得分:0)

您可以使用内置的“恢复”功能来处理紧急情况并打印堆栈跟踪。

来自https://blog.golang.org/defer-panic-and-recover

  

恢复是一个内置功能,可以重新获得对恐慌的控制   goroutine。恢复仅在延迟函数内部有用。中   正常执行,恢复调用将返回nil并且没有其他值   影响。如果当前的goroutine处于恐慌状态,则将进行恢复呼叫   捕获紧急值并恢复正常执行。

我已修改您的示例以使用restore和eris。 Eris提供了一种更好的方式来处理,跟踪和记录Go中的错误。

package main

import (
    "github.com/rotisserie/eris"
    "fmt"
)

func main() {
    value, err := DoSomething()
    defer func() {
        if r := recover(); r!= nil {
            fmt.Println(fmt.Sprintf("%+v", r))
        }
    }()
    if err != nil {
        panic(err)
    }

    fmt.Println(value)
}

func DoSomething() (string, error) {
    return "", eris.New("some error explanation here")
}

输出为:

some error explanation here
    main.DoSomething: /tmp/sandbox147128055/prog.go: 23
    main.main: /tmp/sandbox147128055/prog.go: 9
    runtime.main: /usr/local/go/src/runtime/proc.go: 203
    runtime.goexit: /usr/local/go/src/runtime/asm_amd64p32.s: 523

在此处https://play.golang.org/p/jgkaR42ub5q

查看实际操作