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 ¶
- Variables
- func AttachStack(err error)
- func CatchPanic(err_ref *error)
- func Finalize(finalizers ...Finalizer) error
- func GetData(err error, key DataKey) interface{}
- func GetExits(err error) string
- func GetMessage(err error) string
- func GetStack(err error) string
- func LogWithStack(messages ...interface{})
- func New(text string) error
- func Record(err error) error
- func RecordBefore(err error, depth int) error
- func WrappedErr(err error) error
- type DataKey
- type EquivalenceOption
- type Error
- func (e *Error) Class() *ErrorClass
- func (e *Error) Error() string
- func (e *Error) Exits() string
- func (e *Error) GetData(key DataKey) interface{}
- func (e *Error) Is(ec *ErrorClass, opts ...EquivalenceOption) bool
- func (e *Error) Message() string
- func (e *Error) Name() (string, bool)
- func (e *Error) Stack() string
- func (e *Error) WrappedErr() error
- type ErrorClass
- func (e *ErrorClass) Contains(err error, opts ...EquivalenceOption) bool
- func (e *ErrorClass) GetData(key DataKey) interface{}
- func (e *ErrorClass) Is(parent *ErrorClass) bool
- func (e *ErrorClass) MustAddData(key DataKey, value interface{})
- func (e *ErrorClass) New(format string, args ...interface{}) error
- func (parent *ErrorClass) NewClass(name string, options ...ErrorOption) *ErrorClass
- func (e *ErrorClass) NewWith(message string, options ...ErrorOption) error
- func (e *ErrorClass) Parent() *ErrorClass
- func (e *ErrorClass) String() string
- func (e *ErrorClass) Wrap(err error, options ...ErrorOption) error
- func (e *ErrorClass) WrapUnless(err error, classes ...*ErrorClass) error
- type ErrorGroup
- type ErrorOption
- type Finalizer
- type LoggingErrorGroup
Constants ¶
This section is empty.
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") ErrorGroupNoCaptureStackError = NewClass("Error Group Error", NoCaptureStack()) )
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 GetData ¶
GetData returns the value associated with the given DataKey on this error or any of its ancestors. Please see the example for SetData
func GetMessage ¶
GetMessage returns just the error message without the backtrace or exits.
func LogWithStack ¶
func LogWithStack(messages ...interface{})
LogWithStack will log the given messages with the current stack
func New ¶
New is for compatibility with the default Go errors package. It simply creates an error from the HierarchicalError root class.
func Record ¶
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 ¶
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 ¶
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().
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 ¶
Error conforms to the error interface. Error will return the backtrace if it was captured and any recorded exits.
func (*Error) Exits ¶
Exits will return the exits recorded on the error if any are found. You probably want the package-level GetExits.
func (*Error) GetData ¶
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) Name ¶
Name returns the name of the error: in this case the name of the class the error belongs to.
func (*Error) Stack ¶
Stack will return the stack associated with the error if one is found. You probably want the package-level GetStack.
func (*Error) WrappedErr ¶
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 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 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.