/* wrappederror provides traceable errors: package main import ( // External werr "git.gibonuddevalla.se/go/wrappederror" // Standard "encoding/json" "errors" "fmt" "os" ) func errorHandler(err werr.Error) { // For example print or log error to file fmt.Printf("\x1b[31;1m%s\x1b[0m\n", err) fmt.Printf("\x1b[33;1m%s\x1b[0m\n", err.NoTrace()) j, _ := json.MarshalIndent(err, "", " ") fmt.Printf("%s\n\n", j) } func main() { // Make file paths relative to this file. werr.Init() // Handler to call when using Log(). werr.SetLogCallback(errorHandler) // Wrap an existing error. err := errors.New("foobar 1") err1 := werr.Wrap(err) // Create a new error with extra information. err2 := werr.New("foobar 2").WithCode("FOO-100").WithData(137) // The os error contains more information. _, errOS := os.ReadFile("/tmp/does_not_exist") werr.Wrap(errOS).Log() // Log the previously wrapped errors. werr.Wrap(err1).Log() werr.Wrap(err2).Log() } */ package WrappedError import ( // Standard "fmt" "path" "regexp" "runtime" ) type Error struct { Wrapped error ErrStr string // wrapped error isn't necessarily json encodable Code string File string Line int Data any } type CodableError interface { error WithCode(string) CodableError WithData(any) CodableError Log() CodableError } type LogCallback func(Error) var ( logCallback LogCallback baseDirLength int ) // Init only works if called from the main package and sets the length of code base path // to later be removed in file paths to receive relative paths. func Init() { _, file, _, _ := runtime.Caller(1) dirBase := path.Dir(file) baseDirLength = len(dirBase) } // SetLogCallback gives a possibility to automatically run code to handle any errors. func SetLogCallback(cbk LogCallback) { logCallback = cbk } func callback(wrapped Error) { if logCallback != nil { logCallback(wrapped) } } // Error implements the error inteface and adds filename and line to the error. func (wrapped Error) Error() string { var code string if wrapped.Code != "" { code = wrapped.Code + ":" } return fmt.Sprintf( "[%s%s:%d] %s", code, wrapped.File, wrapped.Line, wrapped.Wrapped.Error(), ) } func create(err error, data interface{}) *Error { if err == nil { return nil } _, file, line, _ := runtime.Caller(2) file = file[baseDirLength+1:] wrapped := Error{ Wrapped: err, ErrStr: err.Error(), File: file, Line: line, Data: data, } return &wrapped } // Wrap wraps an existing error with file and line. func Wrap(err error) *Error { return create(err, nil) } // New creates a new wrapped error with file and line. func New(msg string, params ...any) *Error { err := fmt.Errorf(msg, params...) wrapped := create(err, nil) return wrapped } // WithCode associates a string code with the error. func (e *Error) WithCode(code string) CodableError { e.Code = code return e } // WithData associates any data with the error to make troubleshooting easier. func (e *Error) WithData(data any) CodableError { e.Data = data return e } // Log calls the log callback. func (e *Error) Log() CodableError { callback(*e) return e } // NoTrace returns the Error() string without the trace. func (e *Error) NoTrace() string { rxp := regexp.MustCompile(`^(\[[^\]]+\.go:\d+\]\s*)*`) return string( rxp.ReplaceAll( []byte(e.Error()), []byte(""), ), ) }