README

errors Build Status

Please see http://godoc.org/github.com/spacemonkeygo/errors for info

License

Copyright (C) 2014 Space Monkey, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Documentation

Overview

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

Constants

This section is empty.

Variables

View Source
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{}{}}
)
View Source
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")
)
View Source
var (
	// Change this method if you want errors to log somehow else
	LogMethod = log.Printf

	ErrorGroupError               = NewClass("Error Group Error")
	ErrorGroupNoCaptureStackError = NewClass("Error Group Error", NoCaptureStack())
)
View Source
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.

Functions

func AttachStack

func AttachStack(err error)

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

func CatchPanic

func CatchPanic(err_ref *error)

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

func Finalize

func Finalize(finalizers ...Finalizer) error

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

func GetData

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

func GetExits(err error) string

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

func GetMessage

func GetMessage(err error) string

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

func GetStack

func GetStack(err error) string

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

func LogWithStack

func LogWithStack(messages ...interface{})

LogWithStack will log the given messages with the current stack

func New

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

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

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

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.

Types

type DataKey

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

func GenSym() DataKey

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

type EquivalenceOption

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

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

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

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

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

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

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

func (e *Error) Message() string

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

func (*Error) Name

func (e *Error) Name() (string, bool)

Name returns the name of the error: in this case the name of the class the error belongs to.

func (*Error) Stack

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

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

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

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

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

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

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

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

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

func (*ErrorClass) MustAddData

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

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

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

func (*ErrorClass) NewClass

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

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

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

func (*ErrorClass) Parent

func (e *ErrorClass) Parent() *ErrorClass

Parent returns this error class' direct ancestor.

func (*ErrorClass) String

func (e *ErrorClass) String() string

String returns this error class' name

func (*ErrorClass) Wrap

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

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

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.

func NewBoundedErrorGroup

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

func NewErrorGroup() *ErrorGroup

NewErrorGroup makes a new ErrorGroup

func NewErrorGroupNoCaptureStack

func NewErrorGroupNoCaptureStack() *ErrorGroup

NewErrorGroup make a new ErrorGroup that doesn't print the stacktrace

func (*ErrorGroup) Add

func (e *ErrorGroup) Add(err error)

Add is called with errors. nil errors are ignored.

func (*ErrorGroup) Finalize

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

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

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

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

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

func NoCaptureStack() ErrorOption

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

func NoLogOnCreation

func NoLogOnCreation() ErrorOption

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

func SetData

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.

type Finalizer

type Finalizer interface {
	Finalize() error
}

type LoggingErrorGroup

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

func NewLoggingErrorGroup(name string) *LoggingErrorGroup

NewLoggingErrorGroup returns a new LoggingErrorGroup with the given name.

func (*LoggingErrorGroup) Add

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

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

Path Synopsis
Package errhttp provides some useful helpers on top of the errors package for HTTP handling.
Package errhttp provides some useful helpers on top of the errors package for HTTP handling.