74

As we all know, panics produce a stacktrace to stdout (Playground link).:

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

And it seems when you recover from a panic, recover() returns only an error which describes what caused the panic (Playground link).

runtime error: index out of range

My question is, is it possible to store the stacktrace which is written to stdout? This provides much better debugging information than the string runtime error: index out of range because it shows the exact line in a file which caused the panic.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
hlin117
  • 20,764
  • 31
  • 72
  • 93
  • 2
    you should read [this](https://stackoverflow.com/questions/33034241/how-to-get-the-stack-trace-pointing-to-actual-error-reason-in-golang) and check for [that](https://godoc.org/github.com/pkg/errors) –  Aug 30 '18 at 18:26
  • 3
    Use package debug. – Volker Aug 30 '18 at 18:37
  • I'd like to think there's a way. For example, I've been using `runtime/debug` to capture a stacktrace, see [here](https://play.golang.org/p/W9fuzDRdNfJ). I'm curious whether other contributors have a better solution though. – hlin117 Aug 30 '18 at 18:37

3 Answers3

103

Like @Volker mentioned above, and what was posted as a comment, we can use the runtime/debug package.

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

prints

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

Playground link.

hlin117
  • 20,764
  • 31
  • 72
  • 93
  • 25
    To make it clear (because this case doesn't explicitly test this): When you're inside recover(), the function debug.Stack() will return the stack OF THE PANIC, not just the lexical stack of the deferred function. – Jon Watte Apr 18 '19 at 19:01
  • @JonWatte in addition, original panic place will be upper in stack trace. – Pavel Patrin Apr 14 '20 at 18:46
  • Is this safe for use in production code? As it sits in the debug package. – DubDub Mar 03 '23 at 10:50
7

Create a log file to add the stacktrace to the file for stdout or stderr. This will add the data including the time with line of the error inside a file.

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

Working example on Go playground

Himanshu
  • 12,071
  • 7
  • 46
  • 61
-2

Here is solution to convert panic into error with stacktrace to be logged like any other error

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 output

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.