Go语言 如何获取指向实际错误原因的堆栈跟踪

beq87vna  于 2023-06-03  发布在  Go
关注(0)|答案(8)|浏览(539)

假设我有这样的代码:

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堆栈跟踪正好在我的代码出现异常的地方结束,因此在我的情况下不是很有用。
我创建了一个playground here,理想情况下,我希望能够跟踪错误到它实际返回的位置,而不是恐慌。(例如,到第17行,return "", errors.New("some error explanation here")
这可能吗

nhn9ugyo

nhn9ugyo1#

我认为有一个更简单的方法来实现这一点。您可以使用golang“default”第三方库错误包尝试wrapping errors
您需要定义error要实现的接口:

type stackTracer interface {
    StackTrace() errors.StackTrace
}

然后在 Package /处理错误时使用它:

err, ok := errors.(stackTracer) // ok is false if errors doesn't implement stackTracer

stack := err.StackTrace()
fmt.Println(stack) // here you'll have your stack trace

根据mrpandey的评论,还有其他允许一定程度的错误处理的库:

  • eris“是一个错误处理库,具有可读的堆栈跟踪和灵活的格式支持”
  • go-errors/errors“为go中的错误添加了stacktrace支持。”
  • palantir/stacktrace“看看Palantir,这样一个Java商店。我不敢相信他们想在Go代码中使用堆栈跟踪。
8fsztsew

8fsztsew2#

简而言之:这是不可能的。由于errors are values,它们不会以任何特殊的方式处理。因此,当函数(通常)返回时,堆栈不再可用(即。另一个函数调用可以重写返回错误函数堆栈所使用的存储器)。
go1.5中引入了一个名为 trace 的工具,但目前还没有全面的教程,我发现的任何教程都没有说会包含这种功能。

mftmpeh8

mftmpeh83#

正如其他人所指出的,在go中跟踪错误并不简单。有一些像juju/errgo这样的项目,允许您 Package 错误,然后跟踪这些错误。为了让它工作得坚韧,你必须在整个项目中一致地使用它们,这对你处理第三方库中的错误或处理后永远不会返回的错误没有帮助。
因为这是一个很常见的问题,我真的对此很恼火,我写了一个小的debug utility,它将添加调试代码到go文件中,记录每个返回的错误(实现error的值)以及返回到STDOUT的函数(如果你需要更高级的日志记录,只需在项目中破解日志记录器,这真的很简单)。

安装

go get github.com/gellweiler/errgotrace

用法

要调试当前目录中的所有文件,请执行以下操作:

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

要从go文件中删除添加的调试代码,请执行以下操作:

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

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

输出示例

[...]
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
[...]

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

kknvjkwl

kknvjkwl4#

查看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)
}

下面是输出:

1szpjjfi

1szpjjfi5#

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

aij0ehis

aij0ehis6#

请看: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)
myss37ts

myss37ts7#

据我所知,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 )
new9mtju

new9mtju8#

您可以使用内置的Recover函数来处理死机并打印堆栈跟踪。
https://blog.golang.org/defer-panic-and-recover
Recover是一个内置函数,可以重新控制一个惊慌失措的goroutine。Recover仅在延迟函数中有用。在正常执行期间,调用recover将返回nil,并且没有其他效果。如果当前的goroutine是panicking,调用recover将捕获panic的值并恢复正常执行。
我修改了你的例子,使用recover和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

相关问题