Package xerr
provides an error with stack trace, a multi-error and other different types of errors.
$ go get -u github.com/actforgood/xerr
- an error enriched with stack trace
- a MultiError
Basic example:
// create a new error with stack trace and print it.
err := xerr.New("something went bad")
fmt.Printf("%+v", err)
Output example:
something went bad
github.com/actforgood/xerr/_example/pkgb.OperationB
/Users/bogdan/work/go/xerr/_example/pkgb/otherfile.go:7
github.com/actforgood/xerr/_example/pkga.OperationA
/Users/bogdan/work/go/xerr/_example/pkga/somefile.go:6
main.main
/Users/bogdan/work/go/xerr/_example/main.go:14
runtime.main
/usr/local/go/src/runtime/proc.go:225
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1371
Wrap another error example:
err := DoSomeOperation()
if err != nil {
err = xerr.Wrap(err, "could not perform operation")
}
Output example:
could not perform operation: op err
github.com/actforgood/xerr/_example/pkgb.OperationB
/Users/bogdan/work/go/xerr/_example/pkgb/otherfile.go:14
github.com/actforgood/xerr/_example/pkga.OperationA
/Users/bogdan/work/go/xerr/_example/pkga/somefile.go:6
main.main
/Users/bogdan/work/go/xerr/_example/main.go:14
runtime.main
/usr/local/go/src/runtime/proc.go:225
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1371
You can reduce the I/O bytes and/or storage for your (logged) errors by shrinking the output of stack traces.
The package provides ways of manipulating the function name and excluding frames from the stack trace.
- Example of excluding frames like /usr/local/go/src/ (which is my GOROOT src path):
// somewhere in your application bootstrap:
func init() {
xerr.SetSkipFrame(xerr.SkipFrameGoRootSrcPath(xerr.AllowFrame))
}
Let's see how error's output looks like now:
something went bad
github.com/actforgood/xerr/_example/pkgb.OperationB
/Users/bogdan/work/go/xerr/_example/pkgb/otherfile.go:11
github.com/actforgood/xerr/_example/pkga.OperationA
/Users/bogdan/work/go/xerr/_example/pkga/somefile.go:6
main.main
/Users/bogdan/work/go/xerr/_example/main.go:15
You can implement other rules of exclusion by yourself, and even chain multiple rules. Check SkipFrame
and SkipFrameChain
.
- Example of saving some bytes by shorting the function name.
// somewhere in your application bootstrap:
func init() {
xerr.SetFrameFnNameProcessor(xerr.ShortFunctionName)
}
Let's see how error's output looks like now:
something went bad
pkgb.OperationB
/Users/bogdan/work/go/xerr/_example/pkgb/otherfile.go:11
pkga.OperationA
/Users/bogdan/work/go/xerr/_example/pkga/somefile.go:6
main.main
/Users/bogdan/work/go/xerr/_example/main.go:16
Check also other function names shrinkers: OnlyFunctionName
, NoDomainFunctionName
.
- Tip: you can also shrink the filenames. This is not covered by this pkg, but you can achieve it by a go build/run flag. You can read this article.
go run -gcflags "all=-trimpath=/Users/bogdan/work/go" /Users/bogdan/work/go/xerr/_example/main.go
something went bad
pkgb.OperationB
xerr/_example/pkgb/otherfile.go:11
pkga.OperationA
xerr/_example/pkga/somefile.go:6
main.main
xerr/_example/main.go:16
You can collect multiple errors into a MultiError
which implements error
interface.
Basic sequential example:
files := []string{
"/this/file/does/not/exist/1",
"/this/file/does/not/exist/2",
"/this/file/does/not/exist/3",
}
var multiErr *xerr.MultiError // save allocation if Add is never called.
for _, file := range files {
if _, err := os.Open(file); err != nil {
multiErr = multiErr.Add(err) // store allocated multiErr.
}
// else do something with that file ...
}
err := multiErr.ErrOrNil()
fmt.Println(err)
return err
Output example:
error #1
open /this/file/does/not/exist/1: no such file or directory
error #2
open /this/file/does/not/exist/2: no such file or directory
error #3
open /this/file/does/not/exist/3: no such file or directory
Basic parallel example:
files := []string{
"/this/file/does/not/exist/1",
"/this/file/does/not/exist/2",
"/this/file/does/not/exist/3",
}
multiErr := xerr.NewMultiError() // we need instance already initialized.
var wg sync.WaitGroup
for _, file := range files {
wg.Add(1)
go func(filePath string, mErr *xerr.MultiError, waitGr *sync.WaitGroup) {
defer waitGr.Done()
if _, err := os.Open(filePath); err != nil {
_ = mErr.Add(err) // we can dismiss returned value as multiErr is already initialized.
}
// else do something with that file ...
}(file, multiErr, &wg)
}
wg.Wait()
returnErr := multiErr.ErrOrNil()
fmt.Println(returnErr)
Output example:
error #1
open /this/file/does/not/exist/3: no such file or directory
error #2
open /this/file/does/not/exist/1: no such file or directory
error #3
open /this/file/does/not/exist/2: no such file or directory
Feel free to use this pkg if you like it and fits your needs.
Check also other stack aware errors packages like pkg\errors, go-errors\errors.
For multi-error there are hashicorp\go-multierror, uber-go\multierr.
Here stands some benchmarks made locally for stacked error (note though each package err output may be different):
goos: darwin
goarch: amd64
pkg: github.com/actforgood/xerr
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkNewPkgErrors-8 1409167 4396 ns/op 680 B/op 11 allocs/op
BenchmarkNewGoErrors-8 48172 126978 ns/op 21255 B/op 71 allocs/op
BenchmarkNewXerr-8 2409250 2495 ns/op 656 B/op 7 allocs/op
// code snippet for other packages bench
import (
"fmt"
"testing"
goErr "github.com/go-errors/errors"
pkgErr "github.com/pkg/errors"
)
func BenchmarkNewPkgErrors(b *testing.B) {
for n := 0; n < b.N; n++ {
err := pkgErr.New("some error with stack trace")
_ = fmt.Sprintf("%+v", err)
}
}
func BenchmarkNewGoErrors(b *testing.B) {
for n := 0; n < b.N; n++ {
err := goErr.New("some error with stack trace")
_ = err.ErrorStack()
}
}
This package is released under a MIT license. See LICENSE.