errors

package module
v0.1.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2023 License: Apache-2.0 Imports: 7 Imported by: 8

README

Errors

An error handling package to add additional structured fields to errors. This package helps you keep the only handle errors once rule while not losing context where the error occurred.

Usage

errors.Wrap()

includes a stack trace so logging can report the exact location where the error occurred. Includes Wrapf() and Wrap() variants

return errors.Wrapf(err, "while reading '%s'", fileName)
errors.Stack()

Identical to errors.Wrap() but you don't need a message, just a stack trace to where the error occurred.

return errors.Stack(err)
errors.Fields{}

Attach additional fields to the error and a stack trace to give structured logging as much context to the error as possible. Includes Wrap(), Wrapf(), Stack(), Error() and Errorf() variants

return errors.Fields{"fileName": fileName}.Wrapf(err, "while reading '%s'", fileName)
return errors.Fields{"fileName": fileName}.Stack(err)
return errors.Fields{"fileName": fileName}.Error("while reading")
errors.WrapFields()

Works just like errors.Fields{} but allows collecting and passing around fields independent of the point of error creation. In functions with many exit points this can result in cleaner less cluttered looking code.

fields := map[string]any{
    "domain.id": domainId,
}
err, accountID := account.GetByDomain(domainID)
if err != nil {
    // Error only includes `domain.id`
    return errors.WrapFields(err, fields, "during call to account.GetByDomain()")
}
fields["account.id"] = accountID

err, disabled := domain.Disable(accountID, domainID)
if err != nil {
    // Error now includes `account.id` and `domain.id`
    return errors.WrapFields(err, fields, "during call to domain.Disable()")
}
errors.Last()

Works just like errors.As() except it returns the last error in the chain instead of the first. In this way you can discover the target which is closest to where the error occurred.

// Returns the last error in the chain that has a stack trace attached
var last callstack.HasStackTrace
if errors.Last(err, &last)) {
	fmt.Printf("Error occurred here: %+v", last.StackTrace())
}
errors.ToMap()

A convenience function to extract all stack and field information from the error.

err := io.EOF
err = errors.WithFields{"fileName": "file.txt"}.Wrap(err, "while reading")
m := errors.ToMap(err)
fmt.Printf("%#v\n", m)
// OUTPUT
// map[string]interface {}{
//   "excFileName":"/path/to/wrap_test.go",
//   "excFuncName":"my_package.ReadAFile",
//   "excLineNum":42,
//   "excType":"*errors.errorString",
//   "excValue":"while reading: EOF",
//   "fileName":"file.txt"
//  }
errors.ToLogrus()

A convenience function to extract all stack and field information from the error in a form appropriate for logrus.

err := io.EOF
err = errors.WithFields{"fileName": "file.txt"}.Wrap(err, "while reading")
f := errors.ToLogrus(err)
logrus.WithFields(f).Info("test logrus fields")
// OUTPUT
// time="2023-02-20T19:11:05-06:00"
//   level=info
//   msg="test logrus fields"
//   excFileName=/path/to/wrap_test.go
//   excFuncName=my_package.ReadAFile
//   excLineNum=21
//   excType="*errors.wrappedError"
//   excValue="while reading: EOF"

Convenience to std error library methods

Provides pass through access to the standard errors.Is(), errors.As(), errors.Unwrap() so you don't need to import this package and the standard error package.

Supported by internal tooling

If you are working at mailgun and are using scaffold; using logrus.WithError(err) will cause logrus to automatically retrieve the fields attached to the error and index them into our logging system as separate searchable fields.

Perfect for passing additional information to http handler middleware

If you have custom http middleware for handling unhandled errors, this is an excellent way to easily pass additional information about the request up to the error handling middleware.

Support for standard golang introspection functions

Errors wrapped with errors.WithFields{} are compatible with standard library introspection functions errors.Unwrap(), errors.Is() and errors.As()

ErrQuery := errors.New("query error")
wrap := errors.WithFields{"key1": "value1"}.Wrap(err, "message")
errors.Is(wrap, ErrQuery) // == true

Proper Usage

The fields wrapped by errors.WithFields{} are not intended to be used to by code to decide how an error should be handled. It is intended as a convenience where the failure is well known, but the context is dynamic. In other words, you know the database returned an unrecoverable query error, but you want to attach localized context information to the error.

As an example

func (r *Repository) FetchAuthor(customerID, isbn string) (Author, error) {
    // Returns ErrorNotFound{} if not exist
    book, err := r.fetchBook(isbn)
    if err != nil {
        return nil, errors.WithFields{"customer.id": customerID, "isbn": isbn}.Wrap(err, "while fetching book")
    }
    // Returns ErrorNotFound{} if not exist
    author, err := r.fetchAuthorByBook(book)
    if err != nil {
        return nil, errors.WithFields{"customer.id" customerID, "book": book}.Wrap(err, "while fetching author")
    }
    return author, nil
}

Now you can easily search your structured logs for errors related to customer.id.

You should continue to create and inspect custom error types


type ErrAuthorNotFound struct {
    Msg string
}

func (e *ErrAuthorNotFound) Error() string {
    return e.Msg
}

func (e *ErrAuthorNotFound) Is(target error) bool {
    _, ok := target.(*NotFoundError)
    return ok
}

func main() {
    r := Repository{}
    author, err := r.FetchAuthor("isbn-213f-23422f52356")
    if err != nil {
        // Fetch the original and determine if the error is recoverable
        if error.Is(err, &ErrAuthorNotFound{}) {
            author, err := r.AddBook("isbn-213f-23422f52356", "charles", "darwin")
        }
        if err != nil {
            logrus.WithFields(errors.ToLogrus(err)).
				WithError(err).Error("while fetching author")
            os.Exit(1)
        }
    }
    fmt.Printf("Author %+v\n", author)
}

Documentation

Index

Constants

View Source
const NoMsg = ""

NoMsg is a small indicator in the code that "" is intentional and there is no message include with the Wrap()

Variables

This section is empty.

Functions

func As

func As(err error, target any) bool

As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true.

func Cause added in v0.1.5

func Cause(err error) error

Cause returns the last error in the stack of wrapped errors.

func Errorf added in v0.1.4

func Errorf(format string, a ...any) error

Errorf formats according to a format specifier and returns the string as a value that satisfies error.

If the format specifier includes a %w verb with an error operand, the returned error will implement an Unwrap method returning the operand. It is invalid to include more than one %w verb or to supply it with an operand that does not implement the error interface. The %w verb is otherwise a synonym for %v.

func Is

func Is(err, target error) bool

Is reports whether any error in err's chain matches target.

func Last

func Last(err error, target any) bool

Last finds the last error in err's chain that matches target, and if one is found, sets target to that error value and returns true. Otherwise, it returns false.

The chain consists of err itself followed by the sequence of errors obtained by repeatedly calling Unwrap.

An error matches target if the error's concrete value is assignable to the value pointed to by target, or if the error has a method `As(any) bool` such that As(target) returns true.

An error type might provide an As() method so it can be treated as if it were a different error type.

Last panics if target is not a non-nil pointer to either a type that implements error, or to any interface type.

NOTE: Last() is much slower than As(). Therefore As() should always be used unless you absolutely need Last() to retrieve the last error in the error chain that matches the target.

func New

func New(text string) error

New returns an error that formats as the given text. Each call to New returns a distinct error value even if the text is identical.

func Stack added in v0.1.4

func Stack(err error) error

Stack annotates err with a stack trace at the point Stack was called. If err is nil, Stack returns nil.

func ToLogrus

func ToLogrus(err error) logrus.Fields

ToLogrus Returns the context and stacktrace information for the underlying error as logrus.Fields{} returns empty logrus.Fields{} if err has no context or no stacktrace

logrus.Fields(errors.ToLogrus(err)).WithField("tid", 1).Error(err)

func ToMap

func ToMap(err error) map[string]any

ToMap Returns the fields for the underlying error as map[string]any If no fields are available returns nil

func Unwrap

func Unwrap(err error) error

Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.

func Wrap

func Wrap(err error, msg string) error

Wrap wraps the error and attaches stack information to the error

func WrapFields added in v0.1.4

func WrapFields(err error, f Fields, msg string) error

WrapFields returns a new error wrapping the provided error with fields and a message.

func WrapFieldsf added in v0.1.4

func WrapFieldsf(err error, f Fields, format string, args ...any) error

WrapFieldsf is identical to WrapFields but with optional formatting

func Wrapf

func Wrapf(err error, format string, a ...any) error

Wrapf is identical to Wrap but formats the error before wrapping.

Types

type Fields added in v0.1.4

type Fields map[string]any

Fields Creates errors that conform to the `HasFields` interface

func (Fields) Error added in v0.1.4

func (f Fields) Error(msg string) error

func (Fields) Errorf added in v0.1.4

func (f Fields) Errorf(format string, args ...any) error

func (Fields) Stack added in v0.1.4

func (f Fields) Stack(err error) error

Stack returns an error annotating err with a stack trace at the point Stack is called. If err is nil, Stack returns nil.

func (Fields) Wrap added in v0.1.4

func (f Fields) Wrap(err error, msg string) error

Wrap returns an error annotating err with a stack trace at the point Wrap is called, and the supplied message. If err is nil, Wrap returns nil.

func (Fields) Wrapf added in v0.1.4

func (f Fields) Wrapf(err error, format string, args ...any) error

Wrapf returns an error annotating err with a stack trace at the point Wrapf is call, and the format specifier. If err is nil, Wrapf returns nil.

type HasFields

type HasFields interface {
	HasFields() map[string]any
}

HasFields Implement this interface to pass along unstructured context to the logger. It is the responsibility of Fields() implementation to unwrap the error chain and collect all errors that have `HasFields()` defined.

type HasFormat

type HasFormat interface {
	Format(st fmt.State, verb rune)
}

HasFormat True if the interface has the format method (from fmt package)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL