How to get the stack trace pointing to actual error reason

前端 未结 8 1685
夕颜
夕颜 2020-12-18 19:42

Let\'s say I have some code like this:

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

In case err != ni

相关标签:
8条回答
  • 2020-12-18 19:54

    Shortly: this is not possible. Since errors are values, they are not treated in any special way. Due to this, when function (normally) returns, stack is no more available (ie. another function call may overwrite memory used by returning-error function' stack).

    There is a tool called trace which was introduced with go1.5, but for now, there is no comprehensive tutorial available neither any of those I found says that this kind of feature will be included.

    0 讨论(0)
  • 2020-12-18 19:55

    I think that there is an easier way to achieve this. You can try wrapping errors using the golang "default" error package:

    You need to define the interface to be implemented by your error :

    type stackTracer interface {
        StackTrace() errors.StackTrace
    }
    

    Then use it when wrapping/treating an error :

    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
    
    0 讨论(0)
  • 2020-12-18 20:04

    Take a look at: https://github.com/efimovalex/stackerr

    Is this the thing you are looking for?

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

    Result:

    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)
    
    0 讨论(0)
  • 2020-12-18 20:08

    As I know, stackrerror is the simplest stack display package. You can use all the native log library records or output the call stack yourself. For example:

        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
        }
    
    

    output :

    *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 )
    
    0 讨论(0)
  • 2020-12-18 20:09

    As others have pointed out tracing errors in go isn't trivial. There are projects like juju/errgo, that allow you to wrap errors and then trace these errors back. For that to work tough, you must use them consistently throughout your project and that won't help you with errors in 3rd party libraries or with errors that get handled and never get returned.

    Because this is such a common issue and I really got annoyed with this, I wrote a small debug utility that will add debug code to go files that logs every returned error (value that implements error) and the function in which it was returned to STDOUT (if you need more advanced logging just hack the logger in the project, it's really simple).

    Installation

    go get github.com/gellweiler/errgotrace
    

    Usage

    To debug all files in the current directory:

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

    To remove the added debug code from the go files:

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

    Then just simply compile & run your code or your test cases.

    Sample Output

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

    As you can see from this output, it's really easy to tell in which function the error originally occurred. Once you know that, you can use a debugger to get more context.

    0 讨论(0)
  • 2020-12-18 20:11

    You can use the built-in Recover function to handle panic and print the stack trace.

    From 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.

    I have modified your example to use recover and eris. Eris provides a better way to handle, trace, and log errors in 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")
    }
    

    The output is:

    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
    

    See it in action here https://play.golang.org/p/jgkaR42ub5q

    0 讨论(0)
提交回复
热议问题