calltrace.go | ||
doc.go | ||
error_test.go | ||
error_tmpl_test.go | ||
error_tmpl.go | ||
error.go | ||
globals.go | ||
go.mod | ||
must.go | ||
mustify.go | ||
README.md | ||
utils.go |
adverr
Package adverr implements errors with call stack traces as well as error templates for error equality
Installation
go get git.tordarus.net/Tordarus/adverr
Usage examples
Import
import "git.tordarus.net/Tordarus/adverr"
Create error templates
var (
ErrDoStuffFailed = adverr.NewErrTmpl("ErrDoStuffFailed", "Could'nt do stuff because of %s")
)
Create independent error (without error template)
func doStuffWithIndependentErr() error {
return adverr.New("Could'nt do stuff")
}
Create error based on template
func doStuff() error {
return ErrDoStuffFailed.New("reasons")
}
Print errors on stderr convieniently
adverr.Print(myErr)
adverr.Println(myErr)
Print errors on stderr and exit with exit code
adverr.Fatal(myErr, 1)
adverr.Fatalln(myErr, 1)
Advantages of error templates
two errors made by the same template will return true when called with errors.Is()
func doStuffAndFailWithMsg(msg string) error {
return ErrDoStuffFailed.New(msg)
}
err1 := doStuffAndFailWithMsg("err1")
err2 := doStuffAndFailWithMsg("err2")
fmt.Println(errors.Is(err1, err2)) // true
fmt.Println(err1 == err2) // false
fmt.Println(err1.Error() == err2.Error()) // false
Wrap errors (Causality of errors)
By wrapping errors, you can provide an error that is caused by another error.
A 'Caused by' section will be printed in the stack trace showing the original error.
You can also retrieve the original error by using errors.Unwrap()
func doStuffWrapped() error {
err := doStuff()
if err != nil {
return adverr.Wrap("doStuffWrapped failed", err)
}
return nil
}
Chain errors (Errors caused in succession)
By chaining errors, you can provide an error that represents multiple errors caused in succession inside the same function. For each chained error a 'Previously thrown' section will be printed in the stack trace. You can programmatically check chained errors using the following methods:
// Get Returns the first error in the chain for which errors.Is(target) returns true
Get(target error) error
// GetByIndex returns the i'th error in the chain
GetByIndex(i int) error
// Chain returns a slice of all chained errors
Chain() []error
// Contains is a shorthand for Get(target) != nil.
// Can be considered as an errors.Is function but for chains instead of causes
Contains(target error) bool
Be aware that the standard library calls wrapped errors chains as well! But these chains are something different. Here is an example use case:
You have a list of files from which you only want to read the first one you have read permissions for. This is most likely done in a loop inside the same function. A chained error can keep all previously failed read errors and show them in a debuggable way. Wrapping by causality would be ambiguous because they might already have been wrapped multiple times and their causes can therefore not be distinguished from previously failed errors (chained errors).
Retrieve call stack trace (for debugging purposes)
fmt.Println(adverr.Trace())
Example of a printed error
Code:
package main
import (
"adverr"
"errors"
)
var (
ErrDoStuffFailed = adverr.NewErrTmpl("ErrDoStuffFailed", "Could'nt do stuff because of %s")
)
func main() {
err := doStuffInAnotherGoroutine()
if err != nil {
adverr.Fatalln(err, 1)
}
}
func doStuff() error {
err := doGoNativeStuff()
if err != nil {
return ErrDoStuffFailed.Wrap(err, "reasons")
}
return nil
}
func doStuffInAnotherGoroutine() error {
ch := make(chan error, 1)
go func() {
ch <- doStuff()
close(ch)
}()
err := <-ch
if err != nil {
return adverr.Wrap("Goroutine failed because of errors", err)
}
return nil
}
func doGoNativeStuff() error {
return errors.New("some go error")
}
Output:
adverr.Error: Goroutine failed because of errors
at main.doStuffInAnotherGoroutine (/home/user/go/src/test/main.go:38)
at main.main (/home/user/go/src/test/main.go:13)
at runtime.main (/usr/local/go/src/runtime/proc.go:204)
Caused by ErrDoStuffFailed: Could'nt do stuff because of reasons
at main.doStuff (/home/user/go/src/test/main.go:22)
at main.doStuffInAnotherGoroutine.func1 (/home/user/go/src/test/main.go:32)
Caused by errors.errorString: some go error
(Unknown source)
Globals
You can set the maximum limit of the call stack trace via
adverr.CallStackLength = 50 // default value: 100
If you are in a productive environment, consider disabling call traces completely for performance reasons:
adverr.DisableTrace = true // default value: false
Change log
v0.1.2
Introduced error chaining
v0.1.1
Improved errors.Is behavior so that ErrTmpl's are considered as targets as well. Example:
err := ErrDoStuffFailed.New("some error")
fmt.Println(errors.Is(err, ErrDoStuffFailed)) // returns true since v0.1.1
v0.1.0
initial release