initial commit
This commit is contained in:
commit
4e5775fcfa
46
calltrace.go
Normal file
46
calltrace.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package adverr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CallTrace represents a call stack trace similar to Java's stack trace
|
||||||
|
type CallTrace struct {
|
||||||
|
frames *runtime.Frames
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace returns a new CallTrace starting from this call
|
||||||
|
// Use skip to skip the first entries in the trace
|
||||||
|
func Trace(skip int) *CallTrace {
|
||||||
|
if !TraceCallStack {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := make([]uintptr, CallStackLength)
|
||||||
|
n := runtime.Callers(skip+1, pc)
|
||||||
|
pc = pc[:n]
|
||||||
|
return &CallTrace{runtime.CallersFrames(pc)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *CallTrace) String() string {
|
||||||
|
if ct == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
b := new(strings.Builder)
|
||||||
|
|
||||||
|
for frame, ok := ct.frames.Next(); ok; frame, ok = ct.frames.Next() {
|
||||||
|
b.WriteString("\tat ")
|
||||||
|
b.WriteString(frame.Function)
|
||||||
|
b.WriteString(" (")
|
||||||
|
b.WriteString(frame.File)
|
||||||
|
b.WriteString(":")
|
||||||
|
b.WriteString(strconv.Itoa(frame.Line))
|
||||||
|
b.WriteString(")")
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
34
doc.go
Normal file
34
doc.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Package adverr implements errors with call stack traces
|
||||||
|
as well as error templates for error equality
|
||||||
|
|
||||||
|
Usage examples
|
||||||
|
|
||||||
|
Creating templates:
|
||||||
|
var (
|
||||||
|
ErrDoStuffFailed = adverr.NewErrTmpl("ErrDoStuffFailed", "Could'nt do stuff because of %s")
|
||||||
|
)
|
||||||
|
|
||||||
|
Creating independent error (without error template):
|
||||||
|
func doStuffWithIndependentErr() error {
|
||||||
|
return adverr.New("Could'nt do stuff")
|
||||||
|
}
|
||||||
|
|
||||||
|
Creating error based on template:
|
||||||
|
func doStuff() error {
|
||||||
|
return ErrDoStuffFailed.New("reasons")
|
||||||
|
}
|
||||||
|
|
||||||
|
Printing errors on stderr convieniently:
|
||||||
|
Print(myErr)
|
||||||
|
Println(myErr)
|
||||||
|
|
||||||
|
Printing errors on stderr and exit with exitcode:
|
||||||
|
Fatal(myErr, 1)
|
||||||
|
Fatalln(myErr, 1)
|
||||||
|
|
||||||
|
Advantages of error templates
|
||||||
|
two errors made by the same template will return true when called with errors.Is()
|
||||||
|
|
||||||
|
*/
|
||||||
|
package adverr
|
104
error.go
Normal file
104
error.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package adverr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error is a wrapper for error with stack trace
|
||||||
|
type Error struct {
|
||||||
|
msg string
|
||||||
|
callTrace *CallTrace
|
||||||
|
tmpl *ErrTmpl
|
||||||
|
cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Error with the given message
|
||||||
|
func New(msg string) *Error {
|
||||||
|
return &Error{
|
||||||
|
msg: msg,
|
||||||
|
cause: nil,
|
||||||
|
tmpl: nil,
|
||||||
|
callTrace: Trace(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns a new Error with the given message which is caused by cause
|
||||||
|
func Wrap(msg string, cause error) *Error {
|
||||||
|
return &Error{
|
||||||
|
msg: msg,
|
||||||
|
cause: cause,
|
||||||
|
callTrace: Trace(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func errtype(err error) string {
|
||||||
|
if e, ok := err.(*Error); ok && e.tmpl != nil {
|
||||||
|
return errtype(e.tmpl)
|
||||||
|
} else if tmpl, ok := err.(*ErrTmpl); ok {
|
||||||
|
t := reflect.TypeOf(tmpl)
|
||||||
|
for t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return t.PkgPath() + "." + tmpl.name
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(err)
|
||||||
|
for t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
return t.PkgPath() + "." + t.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Unwrap() error {
|
||||||
|
return e.cause
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
b := new(strings.Builder)
|
||||||
|
printErr(e, b)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is implements the error equality function used by errors.Is()
|
||||||
|
// It returns true if the error is the same instance or is created using the same ErrTmpl
|
||||||
|
func (e *Error) Is(target error) bool {
|
||||||
|
// same error instance
|
||||||
|
if target == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// no template used, therefore no equality possible
|
||||||
|
if e.tmpl == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// same template, therefore errors are equal to another
|
||||||
|
if tErr, ok := target.(*Error); ok {
|
||||||
|
return tErr.tmpl == e.tmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func printErr(err error, b *strings.Builder) {
|
||||||
|
if e, ok := err.(*Error); ok {
|
||||||
|
b.WriteString(errtype(e))
|
||||||
|
b.WriteString(": ")
|
||||||
|
b.WriteString(e.msg)
|
||||||
|
b.WriteString("\n")
|
||||||
|
b.WriteString(e.callTrace.String())
|
||||||
|
} else {
|
||||||
|
b.WriteString(errtype(err))
|
||||||
|
b.WriteString(": ")
|
||||||
|
b.WriteString(err.Error())
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
cause := errors.Unwrap(err)
|
||||||
|
if cause != nil {
|
||||||
|
b.WriteString("Caused by ")
|
||||||
|
printErr(cause, b)
|
||||||
|
}
|
||||||
|
}
|
17
error_test.go
Normal file
17
error_test.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package adverr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestErr(t *testing.T) {
|
||||||
|
TraceCallStack = false
|
||||||
|
err := doStuff()
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doStuff() error {
|
||||||
|
return Wrap("wrapped error", Wrap("test error", fmt.Errorf("asd: %w", errors.New("test"))))
|
||||||
|
}
|
35
error_tmpl.go
Normal file
35
error_tmpl.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package adverr
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrTmplUsedAsErr is returned from ErrTmpl.Error() because an error template should never be used as an actual error value
|
||||||
|
ErrTmplUsedAsErr = NewErrTmpl("ErrTmplUsedAsErr", "Error template used as error value: %s")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrTmpl is an error template to define equalities between different errors
|
||||||
|
type ErrTmpl struct {
|
||||||
|
name string
|
||||||
|
format string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrTmpl returns a new error template with the given format string as a predefined error message
|
||||||
|
func NewErrTmpl(name, format string) *ErrTmpl {
|
||||||
|
return &ErrTmpl{name, format}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implementation just for satisfying the error interface
|
||||||
|
// Please dont use ErrTmpls as actual errors
|
||||||
|
func (t *ErrTmpl) Error() string {
|
||||||
|
return ErrTmplUsedAsErr.New(errtype(t)).Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Error in which the given values are being formatted into the format string of its template
|
||||||
|
func (t *ErrTmpl) New(args ...interface{}) *Error {
|
||||||
|
return &Error{
|
||||||
|
msg: fmt.Sprintf(t.format, args...),
|
||||||
|
cause: nil,
|
||||||
|
tmpl: t,
|
||||||
|
callTrace: Trace(2),
|
||||||
|
}
|
||||||
|
}
|
19
error_tmpl_test.go
Normal file
19
error_tmpl_test.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package adverr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDoStuffFailed = NewErrTmpl("ErrDoStuffFailed", "test error: %s")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestErrTmpl(t *testing.T) {
|
||||||
|
err := doTemplateStuff()
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTemplateStuff() error {
|
||||||
|
return ErrDoStuffFailed.New("because of reasons")
|
||||||
|
}
|
9
globals.go
Normal file
9
globals.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package adverr
|
||||||
|
|
||||||
|
// TraceCallStack decides if any call stack traces will be gathered when creating Errors
|
||||||
|
// If your application is doing performance-heavy tasks with lots of Error creations, you may consider setting this to false
|
||||||
|
// If set to false, all CallTraces in Error will be nil
|
||||||
|
var TraceCallStack bool = true
|
||||||
|
|
||||||
|
// CallStackLength decides how many calls from the call stack should be gathered at most
|
||||||
|
var CallStackLength int = 100
|
28
utils.go
Normal file
28
utils.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package adverr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Println prints the given err to stderr followed by a newline
|
||||||
|
func Println(err error) {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print prints the given err to stderr
|
||||||
|
func Print(err error) {
|
||||||
|
fmt.Fprint(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalln prints the given err to stderr followed by a newline and exits immediately with the given exit code
|
||||||
|
func Fatalln(err error, exitcode int) {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(exitcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal prints the given err to stderr and exits immediately with the given exit code
|
||||||
|
func Fatal(err error, exitcode int) {
|
||||||
|
fmt.Fprint(os.Stderr, err)
|
||||||
|
os.Exit(exitcode)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user