errors: github.com/spacemonkeygo/errors Index | Examples | Files | Directories

package errors

import "github.com/spacemonkeygo/errors"

Package errors is a flexible error support library for Go

Motivation

Go's standard library is intentionally sparse on providing error utilities, and developers coming from other programming languages may miss some features they took for granted [1]. This package is an attempt at providing those features in an idiomatic Go way.

The main features this package provides (in addition to miscellaneous utilities) are:

* Error hierarchies
* Stack traces
* Arbitrary error values

Error hierarchies

While Go has very deliberately not implemented class hierarchies, a quick perusal of Go's net and os packages should indicate that sometimes error hierarchies are useful. Go programmers should be familiar with the net.Error interface (and the types that fulfill it) as well as the os helper functions such as os.IsNotExist, os.IsPermission, etc.

Unfortunately, to implement something similar, a developer will have to implement a struct that matches the error interface as well as any testing methods or any more detailed interfaces they may choose to export. It's not hard, but it is friction, and developers tend to use fmt.Errorf instead due to ease of use, thus missing out on useful features that functions like os.IsNotExist and friends provide.

The errors package provides reusable components for building similar features while reducing friction as much as possible. With the errors package, the os error handling routines can be mimicked as follows:

package osmimic

import (
  "github.com/spacemonkeygo/errors"
)

var (
  OSError = errors.NewClass("OS Error")
  NotExist = OSError.NewClass("Not Exist")
)

func Open(path string) (*File, error) {
  // actually do something here
  return nil, NotExist.New("path %#v doesn't exist", path)
}

func MyMethod() error {
  fh, err := Open(mypath)
  if err != nil {
    if NotExist.Contains(err) {
      // file doesn't exist, do stuff
    }
    return err
  }
  // do stuff
}

Stack traces

It doesn't take long during Go development before you may find yourself wondering where an error came from. In other languages, as soon as an error is raised, a stack trace is captured and is displayed as part of the language's error handling. Go error types are simply basic values and no such magic happens to tell you what line or what stack an error came from.

The errors package fixes this by optionally (but by default) capturing the stack trace as part of your error. This behavior can be turned off and on for specific error classes and comes in two flavors. You can have the stack trace be appended to the error's Error() message, or you can have the stack trace be logged immediately, every time an error of that type is instantiated.

Every error and error class supports hierarchical settings, in the sense that if a setting was not explicitly set on that error or error class, setting resolution traverses the error class hierarchy until it finds a valid setting, or returns the default.

See CaptureStack()/NoCaptureStack() and LogOnCreation()/NoLogOnCreation() for how to control this feature.

Arbitrary error values

These hierarchical settings (for whether or not errors captured or logged stack traces) were so useful, we generalized the system to allow users to extend the errors system with their own values. A user can tag a specific error with some value given a statically defined key, or tag a whole error class subtree.

Arbitrary error values can easily handle situtations like net.Error's Temporary() field, where some errors are temporary and others aren't. This can be mimicked as follows:

package netmimic

import (
  "github.com/spacemonkeygo/errors"
)

var (
  NetError = errors.NewClass("Net Error")
  OpError = NetError.NewClass("Op Error")

  tempErrorKey = errors.GenSym()
)

func SetIsTemporary() errors.ErrorOption {
  return errors.SetData(tempErrorKey, true)
}

func IsTemporary(err error) bool {
  v, ok := errors.GetData(err, tempErrorKey).(bool)
  if !ok {
    return false
  }
  return v
}

func NetworkOp() error {
  // actually do something here
  return OpError.NewWith("failed operation", SetIsTemporary())
}

func Example() error {
  for {
    err := NetworkOp()
    if err != nil {
      if IsTemporary(err) {
        // probably should do exponential backoff
        continue
      }
      return err
    }
  }
}

HTTP handling

Another great example of arbitrary error value functionality is the errhttp subpackage. See the errhttp source for more examples of how to use SetData/GetData.

The errhttp package really helped clean up our error code. Take a look to see if it can help your error handling with HTTP stacks too.

http://godoc.org/github.com/spacemonkeygo/errors/errhttp

Exit recording

So you have stack traces, which tells you how the error was generated, but perhaps you're interested in keeping track of how the error was handled?

Every time you call errors.Record(err), it adds the current line information to the error's output. As an example:

func MyFunction() error {
  err := Something()
  if err != nil {
    if IsTemporary(err) {
      // manage the temporary error
      return errors.Record(err)
    } else {
      // manage the permanent error
      return errors.Record(err)
    }
  }
}

errors.Record will help you keep track of which error handling branch your code took.

ErrorGroup

There's a few different types of ErrorGroup utilities in this package, but they all work the same way. Make sure to check out the ErrorGroup example.

CatchPanic

CatchPanic helps you easily manage functions that you think might panic, and instead return errors. CatchPanic works by taking a pointer to your named error return value. Check out the CatchPanic example for more.

Footnotes

[1] This errors package started while porting a large Python codebase to Go. https://www.spacemonkey.com/blog/posts/go-space-monkey

Index

Examples

Package Files

config.go ctx17.go data_keys.go doc.go errors.go syscall.go utils.go

Variables

var (
    // HierarchicalError is the base class for all hierarchical errors generated
    // through this class.
    HierarchicalError = &ErrorClass{
        parent: nil,
        name:   "Error",
        data:   map[DataKey]interface{}{captureStack: true}}

    // SystemError is the base error class for errors not generated through this
    // errors library. It is not expected that anyone would ever generate new
    // errors from a SystemError type or make subclasses.
    SystemError = &ErrorClass{
        parent: nil,
        name:   "System Error",
        data:   map[DataKey]interface{}{}}
)
var (
    // Useful error classes
    NotImplementedError = NewClass("Not Implemented Error", LogOnCreation())
    ProgrammerError     = NewClass("Programmer Error", LogOnCreation())
    PanicError          = NewClass("Panic Error", LogOnCreation())

    // The following SystemError descendants are provided such that the GetClass
    // method has something to return for standard library error types not
    // defined through this class.
    //
    // It is not expected that anyone would create instances of these classes.
    //
    // from os
    SyscallError = SystemError.NewClass("Syscall Error")
    // from syscall
    ErrnoError = SystemError.NewClass("Errno Error")
    // from net
    NetworkError        = SystemError.NewClass("Network Error")
    UnknownNetworkError = NetworkError.NewClass("Unknown Network Error")
    AddrError           = NetworkError.NewClass("Addr Error")
    InvalidAddrError    = AddrError.NewClass("Invalid Addr Error")
    NetOpError          = NetworkError.NewClass("Network Op Error")
    NetParseError       = NetworkError.NewClass("Network Parse Error")
    DNSError            = NetworkError.NewClass("DNS Error")
    DNSConfigError      = DNSError.NewClass("DNS Config Error")
    // from io
    IOError            = SystemError.NewClass("IO Error")
    EOF                = IOError.NewClass("EOF")
    ClosedPipeError    = IOError.NewClass("Closed Pipe Error")
    NoProgressError    = IOError.NewClass("No Progress Error")
    ShortBufferError   = IOError.NewClass("Short Buffer Error")
    ShortWriteError    = IOError.NewClass("Short Write Error")
    UnexpectedEOFError = IOError.NewClass("Unexpected EOF Error")
    // from context
    ContextError    = SystemError.NewClass("Context Error")
    ContextCanceled = ContextError.NewClass("Canceled")
    ContextTimeout  = ContextError.NewClass("Timeout")
)
var (
    // Change this method if you want errors to log somehow else
    LogMethod = log.Printf

    ErrorGroupError = NewClass("Error Group Error")
)
var Config = struct {
    Stacklogsize int `default:"4096" usage:"the max stack trace byte length to log"`
}{
    Stacklogsize: 4096,
}

Config is a configuration struct meant to be used with

github.com/spacemonkeygo/flagfile/utils.Setup

but can be set independently.

func AttachStack Uses

func AttachStack(err error)

AttachStack adds another stack to the current error's stack trace if it exists

func CatchPanic Uses

func CatchPanic(err_ref *error)

CatchPanic can be used to catch panics and turn them into errors. See the example.

Code:

panicfn := func() {
    panic("oh hai")
}
nonpanicfn := func() (err error) {
    defer CatchPanic(&err)
    panicfn()
    return nil
}
err := nonpanicfn()
assert(t, PanicError.Contains(err))

func Finalize Uses

func Finalize(finalizers ...Finalizer) error

Finalize takes a group of ErrorGroups and joins them together into one error

func GetData Uses

func GetData(err error, key DataKey) interface{}

GetData returns the value associated with the given DataKey on this error or any of its ancestors. Please see the example for SetData

func GetExits Uses

func GetExits(err error) string

GetExits will return the exits recorded on the error if any are found.

func GetMessage Uses

func GetMessage(err error) string

GetMessage returns just the error message without the backtrace or exits.

func GetStack Uses

func GetStack(err error) string

GetStack will return the stack associated with the error if one is found.

func LogWithStack Uses

func LogWithStack(messages ...interface{})

LogWithStack will log the given messages with the current stack

func New Uses

func New(text string) error

New is for compatibility with the default Go errors package. It simply creates an error from the HierarchicalError root class.

func Record Uses

func Record(err error) error

Record will record the current pc on the given error if possible, adding to the error's recorded exits list. Returns the given error argument.

func RecordBefore Uses

func RecordBefore(err error, depth int) error

RecordBefore will record the pc depth frames above the current stack frame on the given error if possible, adding to the error's recorded exits list. Record(err) is equivalent to RecordBefore(err, 0). Returns the given error argument.

func WrappedErr Uses

func WrappedErr(err error) error

WrappedErr returns the wrapped error, if the current error is simply wrapping some previously returned error or system error. If the error isn't hierarchical it is just returned.

type DataKey Uses

type DataKey struct {
    // contains filtered or unexported fields
}

DataKey's job is to make sure that keys in each error instances namespace are lexically scoped, thus helping developers not step on each others' toes between large packages. You can only store data on an error using a DataKey, and you can only make DataKeys with GenSym().

func GenSym Uses

func GenSym() DataKey

GenSym generates a brand new, never-before-seen DataKey

type EquivalenceOption Uses

type EquivalenceOption int

EquivalenceOption values control behavior of determining whether or not an error belongs to a specific class.

const (
    // If IncludeWrapped is used, wrapped errors are also used for determining
    // class membership.
    IncludeWrapped EquivalenceOption = 1
)

type Error Uses

type Error struct {
    // contains filtered or unexported fields
}

Error is the type that represents a specific error instance. It is not expected that you will work with *Error classes directly. Instead, you should use the 'error' interface and errors package methods that operate on errors instances.

func (*Error) Class Uses

func (e *Error) Class() *ErrorClass

Class will return the appropriate error class for the given error. You probably want the package-level GetClass.

func (*Error) Error Uses

func (e *Error) Error() string

Error conforms to the error interface. Error will return the backtrace if it was captured and any recorded exits.

func (*Error) Exits Uses

func (e *Error) Exits() string

Exits will return the exits recorded on the error if any are found. You probably want the package-level GetExits.

func (*Error) GetData Uses

func (e *Error) GetData(key DataKey) interface{}

GetData returns the value associated with the given DataKey on this error or any of its ancestors. Please see the example for SetData

func (*Error) Is Uses

func (e *Error) Is(ec *ErrorClass, opts ...EquivalenceOption) bool

Is returns whether or not an error belongs to a specific class. Typically you should use Contains instead.

func (*Error) Message Uses

func (e *Error) Message() string

Message returns just the error message without the backtrace or exits.

func (*Error) Stack Uses

func (e *Error) Stack() string

Stack will return the stack associated with the error if one is found. You probably want the package-level GetStack.

func (*Error) WrappedErr Uses

func (e *Error) WrappedErr() error

WrappedErr returns the wrapped error, if the current error is simply wrapping some previously returned error or system error. You probably want the package-level WrappedErr

type ErrorClass Uses

type ErrorClass struct {
    // contains filtered or unexported fields
}

ErrorClass is the basic hierarchical error type. An ErrorClass generates actual errors, but the error class controls properties of the errors it generates, such as where those errors are in the hierarchy, whether or not they capture the stack on instantiation, and so forth.

func GetClass Uses

func GetClass(err error) *ErrorClass

GetClass will return the appropriate error class for the given error. If the error is not nil, GetClass always returns a hierarchical error class, and even attempts to determine a class for common system error types.

func NewClass Uses

func NewClass(name string, options ...ErrorOption) *ErrorClass

NewClass creates an error class with the provided name and options. Classes generated from this method and not *ErrorClass.NewClass will descend from the root HierarchicalError base class.

func (*ErrorClass) Contains Uses

func (e *ErrorClass) Contains(err error, opts ...EquivalenceOption) bool

Contains returns whether or not the receiver error class contains the given error instance.

func (*ErrorClass) GetData Uses

func (e *ErrorClass) GetData(key DataKey) interface{}

GetData will return any data set on the error class for the given key. It returns nil if there is no data set for that key.

func (*ErrorClass) Is Uses

func (e *ErrorClass) Is(parent *ErrorClass) bool

Is returns true if the receiver class is or is a descendent of parent.

func (*ErrorClass) MustAddData Uses

func (e *ErrorClass) MustAddData(key DataKey, value interface{})

MustAddData allows adding data key value pairs to error classes after they are created. This is useful for allowing external packages add namespaced values to errors defined outside of their package. It will panic if the key is already set in the error class.

func (*ErrorClass) New Uses

func (e *ErrorClass) New(format string, args ...interface{}) error

New makes a new error type. It takes a format string.

func (*ErrorClass) NewClass Uses

func (parent *ErrorClass) NewClass(name string,
    options ...ErrorOption) *ErrorClass

NewClass creates an error class with the provided name and options. The new class will descend from the receiver.

func (*ErrorClass) NewWith Uses

func (e *ErrorClass) NewWith(message string, options ...ErrorOption) error

NewWith makes a new error type with the provided error-specific options.

func (*ErrorClass) Parent Uses

func (e *ErrorClass) Parent() *ErrorClass

Parent returns this error class' direct ancestor.

func (*ErrorClass) String Uses

func (e *ErrorClass) String() string

String returns this error class' name

func (*ErrorClass) Wrap Uses

func (e *ErrorClass) Wrap(err error, options ...ErrorOption) error

Wrap wraps the given error in the receiver error class with the provided error-specific options.

func (*ErrorClass) WrapUnless Uses

func (e *ErrorClass) WrapUnless(err error, classes ...*ErrorClass) error

WrapUnless wraps the given error in the receiver error class unless the error is already an instance of one of the provided error classes.

type ErrorGroup Uses

type ErrorGroup struct {
    Errors []error
    // contains filtered or unexported fields
}

ErrorGroup is a type for collecting errors from a bunch of independent tasks. ErrorGroups are not threadsafe. See the example for usage.

Code:

// example utils
work := func(i int) error {
    if i%2 == 0 {
        return nil
    }
    return New("error")
}
handle_err := func(error) {}

// example:
errs := NewErrorGroup()
for i := 0; i < 10; i++ {
    errs.Add(work(i))
}
err := errs.Finalize()
if err != nil {
    handle_err(err)
}

func NewBoundedErrorGroup Uses

func NewBoundedErrorGroup(limit int) *ErrorGroup

NewBoundedErrorGroup makes a new ErrorGroup that will not track more than limit errors. Once the limit is reached, the ErrorGroup will track additional errors as excess.

func NewErrorGroup Uses

func NewErrorGroup() *ErrorGroup

NewErrorGroup makes a new ErrorGroup

func (*ErrorGroup) Add Uses

func (e *ErrorGroup) Add(err error)

Add is called with errors. nil errors are ignored.

func (*ErrorGroup) Finalize Uses

func (e *ErrorGroup) Finalize() error

Finalize will collate all the found errors. If no errors were found, it will return nil. If one error was found, it will be returned directly. Otherwise an ErrorGroupError will be returned.

type ErrorOption Uses

type ErrorOption func(map[DataKey]interface{})

An ErrorOption is something that controls behavior of specific error instances. They can be set on ErrorClasses or errors individually.

func CaptureStack Uses

func CaptureStack() ErrorOption

CaptureStack tells the error class and its descendents to capture the stack whenever an error of this class is created, and output it as part of the error's Error() method. This is the default.

func DisableInheritance Uses

func DisableInheritance() ErrorOption

If DisableInheritance is provided, the error or error class will belong to its ancestors, but will not inherit their settings and options. Use with caution, and may disappear in future releases.

func LogOnCreation Uses

func LogOnCreation() ErrorOption

LogOnCreation tells the error class and its descendents to log the stack whenever an error of this class is created.

func NoCaptureStack Uses

func NoCaptureStack() ErrorOption

NoCaptureStack is the opposite of CaptureStack and applies to the error, class, and its descendents.

func NoLogOnCreation Uses

func NoLogOnCreation() ErrorOption

NoLogOnCreation is the opposite of LogOnCreation and applies to the error, class, and its descendents. This is the default.

func SetData Uses

func SetData(key DataKey, value interface{}) ErrorOption

SetData will take the given value and store it with the error or error class and its descendents associated with the given DataKey. Be sure to check out the example. value can be nil to disable values for subhierarchies.

Code:

// Create our own DataKeys
UserMessageKey := GenSym()
ConstraintNameKey := GenSym()
OtherKey := GenSym()

// Create some error classes
ApplicationError := NewClass("Application Error")
ConstraintError := ApplicationError.NewClass("Constraint Error",
    SetData(UserMessageKey, "A constraint failed on your data"))
ValueConstraintError := ConstraintError.NewClass("Value Constraint Error")

// Create an actual error. Something bad happened.
err := ValueConstraintError.NewWith("value constraint failed!",
    SetData(ConstraintNameKey, "equality_constraint"))

// Make sure everything is how we expect
assert(t, ValueConstraintError.Contains(err))
assert(t, ConstraintError.Contains(err))
assert(t, ApplicationError.Contains(err))
assert(t, !SystemError.Contains(err))

assert(t, GetData(err, UserMessageKey).(string) ==
    "A constraint failed on your data")
assert(t, GetData(err, ConstraintNameKey).(string) == "equality_constraint")
assert(t, GetData(err, OtherKey) == nil)

type Finalizer Uses

type Finalizer interface {
    Finalize() error
}

type LoggingErrorGroup Uses

type LoggingErrorGroup struct {
    // contains filtered or unexported fields
}

LoggingErrorGroup is similar to ErrorGroup except that instead of collecting all of the errors, it logs the errors immediately and just counts how many non-nil errors have been seen. See the ErrorGroup example for usage.

func NewLoggingErrorGroup Uses

func NewLoggingErrorGroup(name string) *LoggingErrorGroup

NewLoggingErrorGroup returns a new LoggingErrorGroup with the given name.

func (*LoggingErrorGroup) Add Uses

func (e *LoggingErrorGroup) Add(err error)

Add will handle a given error. If the error is non-nil, total and failed are both incremented and the error is logged. If the error is nil, only total is incremented.

func (*LoggingErrorGroup) Finalize Uses

func (e *LoggingErrorGroup) Finalize() (err error)

Finalize returns no error if no failures were observed, otherwise it will return an ErrorGroupError with statistics about the observed errors.

Directories

PathSynopsis
errhttpPackage errhttp provides some useful helpers on top of the errors package for HTTP handling.

Package errors imports 12 packages (graph) and is imported by 65 packages. Updated 2017-09-09. Refresh now. Tools for package owners.