Between a rock and a crazy place

go stacktraces

19 Feb 2014

Let's say you write a library in go and want an easy way to get debugging information from your users. Sure, you return errors from everything, but it is sometimes hard to pinpoint where a particular error occured and what caused it. If your package panics, that will give you a stacktrace, but as you probably know you shouldn't panic in case of an error, but just gracefull recover and return the error to your caller.

I recently discovered a pattern which I am quite happy with (for now). You can include a stacktrace when returning an error. If you disable this behaviour by default you should have as good as no impact for normal users, while making it much easier to debug problems. Neat.

package awesomelib

import (
    "os"
    "runtime"
)

type tracedError struct {
    err   error
    trace string
}

var (
    stacktrace bool
    traceSize = 16*1024
)

func init() {
    if os.Getenv("AWESOMELIB_ENABLE_STACKTRACE") == "true" {
        stacktrace = true
    }
}

func wrapErr(err error) error {
    // If stacktraces are disabled, we return the error as is
    if !stacktrace {
        return err
    }

    // This is a convenience, so that we can just throw a wrapErr at every
    // point we return an error and don't get layered useless wrappers
    if Err, ok := err.(*tracedError); ok {
        return Err
    }

    buf := make([]byte, traceSize)
    n := runtime.Stack(buf, false)
    return &tracedError{ err: err, trace: string(buf[:n]) }
}

func (err *tracedError) Error() string {
    return fmt.Sprintf("%v\n%s", err.err, err.trace)
}

func DoFancyStuff(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return wrapErr(err)
    }
    // fancy stuff
}