# errors This is our own implementation of the Golang `error` interface. ## Why Packages tend to use one of the following to return errors: * Standard go ```"errors"``` package with ```errors.New("...")``` * Standard go ```"fmt"``` package with ```fmt.Errorf("...")``` * ```github.com/pkg/errors``` package with ```errors.New("...")``` Of those three, the pkg/errors is by far the better option as it includes an error stack. One could use pkg/errors, but: - it does not offer much flexibility in how it displays errors - its display is cluttered, with the wrong info (e.g. runtime stack and not the error handling stack) - it does not display the up passing of errors in the stack unless you add the stack each time - if you do add the stack each time, it dumps many copies of a stack and becomes cluttered We write our own version of errors, to: - always add the stack - add the correct stack of where we raised/handled the error - make error handling simpler (Wrapf(err) vs WithMessage(WithStack(err))) - give us all the flexibility we need - e.g. give us option to output JSON structured stack For lots of detail on this, see comments in error_formats_test.go and run that test with ```go test -v error_formats_test.go``` ... ## Usage Get the package into your project: ``` go get gitlab.com/uafrica/go-utils ``` ## func New() Fail with your own simple error message: ``` if !authenticated { return errors.New("not authenticated") } ``` ## func Errorf() Fail with your own error using Printf style, e.g. when you detect an invalid value: ``` if limit < 0 { return errors.Errorf("invalid limit %d<0", limit) } ``` ## func Wrapf() Fail when a called function returned an error: ``` if err := db.Select(query); err != nil { return errors.Wrapf(err, "query failed on user=%s", username) } ``` ## Refactoring exiting code Replace all other errors package imports with this package: ``` import ( "gitlab.com/uafrica/go-utils/errors" ) ``` Refactor from standad go "errors" package: - No change: ```errors.New()``` is still supported in this package Refactor from "fmt" package: - Replace `errors.Errorf("my message: %v", err)` with `errors.Wrapf(err, "my message")` so that the layers are preserved and not merged into a single text string. Refactor from "github.com/pkg/errors": - Replace ```errors.WithStack(err)``` with ```errors.New(err)```. - Replace ```return err``` with ```return errors.Wrapf(err, "some message")``` saying what failed as result of the err - Replace ```errors.WithMessagef(err, "...", ...)``` with ```return errors.Wrapf(err, "...", ...)``` - Replace ```errors.WithMessage(err, "...")``` with ```return errors.Wrap(err, "...")``` ## Formatting Report an error with: ``` user,err := getUser(userName) if err != nil { log.Errorf("failed to get user %s: %+v", username, err) ... } ``` Select the appropriate format: - `%+c` in most cases to write the full compact error with file names and line numbers all on one line. - `%+v` is the same when you write to console over multipole lines - `%c` is full error on one line, without reference to source code, i.e. concatenate the error message of all levels e.g. ```A, because B, because C```, which you also get from ```err.Error()```. - `%v` is full error over multipole lines, without references to source code, e.g. ``` A, because B, because C ``` - `%s` is just the last error message. ## JSON error: Call ```err.Description()``` to get a struct that can marshal to JSON for a complete dump, e.g.: ``` { "error": "login failed", "source": { "package": "gitlab.com/uafrica/go-utils/errors/errors_test", "file": "errors_test.go", "line": 18, "function": "TestErrorFormatting" }, "cause": { "error": "failed to find account", "source": { "package": "gitlab.com/uafrica/go-utils/errors/errors_test", "file": "errors_test.go", "line": 17, "function": "TestErrorFormatting" }, "cause": { "error": "query failed", "source": { "package": "gitlab.com/uafrica/go-utils/errors/errors_test", "file": "errors_test.go", "line": 16, "function": "TestErrorFormatting" }, "cause": { "error": "you have problem in your SQL near xxx" } } } } ``` ## func Caller() This function can be used also for logging to determine the caller from the runtime stack. It takes a skip value to skip a few levels of the stack, making it flexible to be called in various wrapper functions, without reporting the wrappers. ## func Stack() This function is similar to Caller() but reports an array of callers, not only one.