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

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

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

1
2
3
4
5
panic: some error explanation here

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

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

需要更多说明:当我来自抛出异常的JVM世界时,我可以完全跟踪抛出异常的确切代码行,从而可以轻松找到错误的地方。 Go堆栈跟踪恰好在我的代码恐慌的地方结束,因此在我的情况下不太有用。

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

这有可能吗?


我认为有一个更简单的方法可以实现这一目标。您可以尝试使用golang"默认"错误包来包装错误:

您需要定义要由error实现的接口:

1
2
3
type stackTracer interface {
    StackTrace() errors.StackTrace
}

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

1
2
3
4
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

很快:这是不可能的。
由于错误是值,因此不会以任何特殊方式处理它们。因此,当函数(通常)返回时,堆栈不再可用(即,另一个函数调用可能会覆盖返回错误函数的堆栈使用的内存)。

go1.5引入了一个称为trace的工具,但目前尚无全面的教程,我发现其中的任何一个都没有说要包含这种功能。

  • 跟踪与该问题完全无关,不确定为什么要提及它。
  • 我可以想象,类似跟踪的工具可以记录当前上下文中任何函数的输入和返回(函数参数或返回值以及返回时的行号)。
  • 我希望它是这种方式,但希望至少可以用一些东西来包装有问题的代码(类似于您提到的跟踪),以获得更详细的堆栈跟踪。
  • 我试图为此使用跟踪没有太大的成功。 因此,这就是为什么我最终编写了自己的小型调试实用程序的原因(请参阅我的答案)。

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

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

这是一个代码示例:

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

  • 似乎不能在最新的GO版本中使用
  • @ Alpha2k我试图在macOS上的go 1.13.4上运行此代码,看来还可以,请您提供更多详细信息?

正如其他人指出的那样,在go中跟踪错误并非易事。有一些项目,例如jujo / errors,使您可以包装错误,然后追溯这些错误。为此,您必须在整个项目中始终使用它们,并且这不会帮助您解决第三方库中的错误或无法处理且永远不会返回的错误。

因为这是一个普遍的问题,我对此感到非常恼火。我写了一个小的调试实用程序,它将添加调试代码到记录每个返回的错误(实现error的值)及其返回到STDOUT的功能的文件中(如果您需要更高级的日志记录,只需在项目,这真的很简单)。

安装

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

用法

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

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

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

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

然后,只需编译并运行您的代码或测试用例即可。

样本输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[...]
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
[...]

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


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

这是您要找的东西吗?

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
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()
}

结果:

1
2
3
4
5
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)
  • 如果您写下行号,这将更有用:)

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

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

Recover is a built-in function that regains control of a panicking
goroutine. Recover is only useful inside deferred functions. During
normal execution, a call to recover will return nil and have no other
effect. If the current goroutine is panicking, a call to recover will
capture the value given to panic and resume normal execution.

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

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

输出为:

1
2
3
4
5
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


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

1
2
3
4
5
6
7
8
9
10
11
12
    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
    }

输出:

1
2
3
4
*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 )