Panics, stack traces and how to recover [best practice]
- A panic is an exception in Go
- Stack traces
- Recover and catch a panic
- Test a panic (utility function)
A panic is an exception in Go
Panics are similar to C++ and Java exceptions,
but are only intended for run-time errors,
such as following a nil pointer or attempting to index an array out of bounds.
To signify events such as end-of-file, Go programs use the built-in error
type.
See Error handling best practice and 3 simple ways to create an error for more on errors.
A panic stops the normal execution of a goroutine:
- When a program panics, it immediately starts to unwind the call stack.
- This continues until the program crashes and prints a stack trace,
- or until the built-in
recover
function is called.
A panic is caused either by a runtime error,
or an explicit call to the built-in panic
function.
Stack traces
A stack trace – a report of all active stack frames – is typically printed to the console when a panic occurs. Stack traces can be very useful for debugging:
- not only do you see where the error happened,
- but also how the program arrived in this place.
Interpret a stack trace
Here’s an example of a stack trace:
goroutine 11 [running]:
testing.tRunner.func1(0xc420092690)
/usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x53f820, 0x594da0)
/usr/local/go/src/runtime/panic.go:491 +0x283
github.com/yourbasic/bit.(*Set).Max(0xc42000a940, 0x0)
../src/github.com/bit/set_math_bits.go:137 +0x89
github.com/yourbasic/bit.TestMax(0xc420092690)
../src/github.com/bit/set_test.go:165 +0x337
testing.tRunner(0xc420092690, 0x57f5e8)
/usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:789 +0x2de
It can be read from the bottom up:
testing.(*T).Run
has calledtesting.tRunner
,- which has called
bit.TestMax
, - which has called
bit.(*Set).Max
, - which has called
panic
, - which has called
testing.tRunner.func1
.
The indented lines show the source file and line number at which the function was called. The hexadecimal numbers refer to parameter values, including values of pointers and internal data structures. Stack Traces in Go has more details.
Print and log a stack trace
To print the stack trace for the current goroutine, use debug.PrintStack
from package runtime/debug
.
You can also examine the current stack trace programmatically by calling runtime.Stack
.
Level of detail
The GOTRACEBACK
variable
controls the amount of output generated when a Go program fails.
GOTRACEBACK=none
omits the goroutine stack traces entirely.GOTRACEBACK=single
(the default) prints a stack trace for the current goroutine, eliding functions internal to the run-time system. The failure prints stack traces for all goroutines if there is no current goroutine or the failure is internal to the run-time.GOTRACEBACK=all
adds stack traces for all user-created goroutines.GOTRACEBACK=system
is likeall
but adds stack frames for run-time functions and shows goroutines created internally by the run-time.
Recover and catch a panic
The built-in recover
function can be used to regain
control of a panicking goroutine and resume normal execution.
- A call to
recover
stops the unwinding and returns the argument passed topanic
. - If the goroutine is not panicking,
recover
returnsnil
.
Because the only code that runs while unwinding is inside deferred functions,
recover
is only useful inside such functions.
Panic handler example
func main() {
n := foo()
fmt.Println("main received", n)
}
func foo() int {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
m := 1
panic("foo: fail")
m = 2
return m
}
foo: fail
main received 0
Since the panic occurred before foo
returned a value,
n
still has its initial zero value.
Return a value
To return a value during a panic, you must use a named return value.
func main() {
n := foo()
fmt.Println("main received", n)
}
func foo() (m int) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
m = 2
}
}()
m = 1
panic("foo: fail")
m = 3
return m
}
foo: fail
main received 2
Test a panic (utility function)
In this example, we use reflection to check if a list of interface variables have types corresponding to the parameters of a given function. If so, we call the function with those parameters to check if there is a panic.
// Panics tells if function f panics with parameters p.
func Panics(f interface{}, p ...interface{}) bool {
fv := reflect.ValueOf(f)
ft := reflect.TypeOf(f)
if ft.NumIn() != len(p) {
panic("wrong argument count")
}
pv := make([]reflect.Value, len(p))
for i, v := range p {
if reflect.TypeOf(v) != ft.In(i) {
panic("wrong argument type")
}
pv[i] = reflect.ValueOf(v)
}
return call(fv, pv)
}
func call(fv reflect.Value, pv []reflect.Value) (b bool) {
defer func() {
if err := recover(); err != nil {
b = true
}
}()
fv.Call(pv)
return
}