Skip to content

Instantly share code, notes, and snippets.

@corebreaker
Last active October 11, 2016 05:28
Show Gist options
  • Save corebreaker/1eb284ddea2af1896c57bf52c56499da to your computer and use it in GitHub Desktop.
Save corebreaker/1eb284ddea2af1896c57bf52c56499da to your computer and use it in GitHub Desktop.
Golang: Verbose errors with stacktraces
/*
Example:
--------
import (
"os"
"log"
"io/ioutil"
"verbose_errors"
)
func read(name string) ([]data, error) {
res, err := ioutil.ReadFile(name)
return res, verbose_errors.WrapError(err)
}
func file_util(name string) error {
data, err := read(name)
if err != nil {
return err
}
content := string(data)
size := len(content)
if size < 50 {
return verbose_errors.MakeError("File with less than 50 characters")
}
log.Printf("Read file %s: %s...", name, content[:40])
return nil
}
func main() {
err := file_util(os.Args[1])
if err != nil {
log.Fatal(err) // Show error with stack trace
}
}
*/
package verbose_errors
import (
"bytes"
"errors"
"fmt"
"runtime"
)
//
/* ------------- Interfaces ------------------------------------------------ */
type StackEntry interface {
fmt.Stringer
GetName() string
GetFile() string
GetLine() uint
}
//
/* ------------- Stack entry implementation -------------------------------- */
type tStackEnry struct {
name string
file string
line uint
}
func (self *tStackEnry) GetName() string { return self.name }
func (self *tStackEnry) GetFile() string { return self.file }
func (self *tStackEnry) GetLine() uint { return self.line }
func (self *tStackEnry) String() string {
return fmt.Sprintf("%s (%s:%d)", self.name, self.file, self.line)
}
//
/* ------------- Error wrapping -------------------------------------------- */
type tVerboseError struct {
source error
message string
trace []StackEntry
}
func (self *tVerboseError) Error() string {
var out bytes.Buffer
fmt.Fprintln(&out, self.source.Error())
fmt.Fprint(&out, self.message)
for _, line := range self.trace {
fmt.Fprintln(&out, " ", line)
}
if len(self.trace) > 0 {
fmt.Fprintln(&out, "------------------------------------------------------------------------------")
}
return out.String()
}
func (self *tVerboseError) add_info(format string, args ...interface{}) error {
var out bytes.Buffer
fmt.Fprintln(&out, fmt.Sprintf(format, args...))
self.message += out.String()
return self
}
func wrap_error(err error) *tVerboseError {
if err == nil {
return nil
}
var pc uintptr = 1
stack := make([]StackEntry, 0)
for i := 1; pc != 0; i++ {
ptr, file, line, ok := runtime.Caller(i)
pc = ptr
if (pc == 0) || (!ok) {
continue
}
f := runtime.FuncForPC(pc)
stack = append(stack, &tStackEnry{name: f.Name(), file: file, line: uint(line)})
}
return &tVerboseError{
source: err,
trace: stack,
}
}
//
/*------------- Public APIs ----------------------------------------------- */
func WrapError(err error) error {
return wrap_error(err)
}
func AddErrorInfo(err error, format string, args ...interface{}) error {
verbose_error, ok := err.(*tVerboseError)
if !ok {
verbose_error = wrap_error(err)
}
return verbose_error.add_info(format, args...)
}
func GetSource(err error) error {
verbose_error, ok := err.(*tVerboseError)
if !ok {
return err
}
return verbose_error.source
}
func GetStackTrace(err error) []StackEntry {
verbose_error, ok := err.(*tVerboseError)
if !ok {
return make([]StackEntry, 0)
}
return verbose_error.trace
}
func MakeError(format string, args ...interface{}) error {
return WrapError(errors.New(fmt.Sprintf(format, args...)))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment