errnie

package module
v1.1.7 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2021 License: Unlicense Imports: 7 Imported by: 0

README

errnie

(Second) Go (at an) error handling package.

One day none of our functions or methods will ever crash again and we will all be drinking half-pints on the beach. Because we're not barbarians.

Installation

go get -u github.com/TheApeMachine/errnie

Basic usage

package main

func main() {
	// Instantiate a new guard object and defer a Recue call.
	guard := NewGuard(errorHandler)
	defer guard.Rescue()()

	// Load the guard with an error.
	guard.Err := errors.New("errors rule, call-stacks drool!")

	// Call Check() to evaluate the error and determine whether
	// or not to short-circuit the function.
	guard.Check()

	// Or simply write some code that panics.
	panic("don't")
}

/*
All code above leads to here.
*/
func errorHandler() {
	// Do some...
}

Usage in a type

package myaveragepackage

/*
JustMy type, really really.
*/
type JustMy struct {
	guard *errnie.Guard
}

/*
NewJustMy returns a pointer to an instance of JustMy.
*/
func NewJustMy() *JustMy {
	jm := JustMy{}
	jm.guard = errnie.NewGuard(jm.handleError)
}

/*
MethodMan the most typical use-case, where you catch your errors inside the guard,
and call a check instead of the standard if statement pattern.
*/
func (jm *JustMy) MethodMan() {
	defer jm.Rescue()()
	// Optionally you can start with calling jm.guard.Check() here if you want to
	// implement the early short-circuit.

	// Make sure we do not have an error and call Check.
	// This will not short-circuit the method.
	jm.guard.Err = nil
	jm.guard.Check()

	// Where the magic happens... This simulates an unhandled
	// panic, and will call the error handler.
	val := 42 / 0
}

/*
handleError, because that's kinda what we're doing here. It's the law.
*/
func (jm *JustMy) handleError() {
	fmt.Println(jm.guard.Err)
}

WIP: Usage with custom errors

package robocop

type NoBulletsLeftError errnie.Error

/*
Weapon wraps the Auto 9 in a type.
I am relatively sure this is how they implemented those infinite magazines
in the 80s/90s action movies.
*/
type Weapon struct {
	bullets uint
	guard errnie.Guard
}

/*
NewWeapon returns a pointer to an instance of Weapon.
*/
func NewWeapon() *Weapon {
	w := Weapon{bullets: 50}
	w.guard = errnie.NewGuard(w.handleError)
}

/*
SprayAndPray. Look at something that moves, vaguely point in that direction
and call in infinite for loop.
*/
func (weapon *Weapon) SprayAndPray() {
	if weapon.bullets == 0 {
		weapon.guard.Err = NoBulletsLeftError
		weapon.guard.Check()
	}

	weapon.bullets--
}

/*
Reload simulates some sort of fault-tolerant method that recovers the state
of the type such that it can resume operation.
*/
func (weapon *Weapon) reload() {
	// Source: https://robocop.fandom.com/wiki/Auto_9
	weapon.bullets = 50
}

/*
handleError should recover the state of the type.
*/
func (weapon *Weapon) handleError {
	if weapon.guard.Err == NoBulletsLeftError {
		weapon.reload()
	}
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Advisor added in v1.1.6

type Advisor interface {
	Static(ring.Ring) bool
	Dynamic(<-chan bytes.Buffer) State
}

Advisor describes an interface that contains two methods of analyzing a buffer of error values. Static analysis is a fast and simple way to make a determination of what in most cases could be fairly described as a "count". Dynamic analysis is a method that takes in a channel where it feeds on additional metadata to get a more refined view of program state. It takes a bytes.Buffer to keep things "generic" so just be sure what you are sending it and hard cast to it, or be a barbarian and use reflection. Remember the calling function right before the advisor though, and that you are basically always looking for a boolean answer to "Are we OK()?".

func NewAdvisor added in v1.1.6

func NewAdvisor(t Advisor) Advisor

NewAdvisor... I'd hate to call it a factory, but it sure seems like one.

type Collector

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

ErrCollector is the canonical implementation of the underlying interface. Use this as a field in your object to handle errors.

func New

func New(advisor *Advisor) *Collector

New acts as a constructor and returns an instance of Collector.

func (Collector) Critical added in v1.1.6

func (collector Collector) Critical(msg ...interface{})

Critical is a pretty printed critical line.

func (Collector) Debug

func (collector Collector) Debug(msg ...interface{})

Debug is a pretty printed info line.

func (Collector) Dump

func (collector Collector) Dump()

Dump the error stack. This prints our the raw Error object data and is not a method you want to use in any code that is not in debug mode.

func (Collector) Error added in v1.1.6

func (collector Collector) Error(msg ...interface{})

Error is a pretty printed error line.

func (Collector) Fatal added in v1.1.6

func (collector Collector) Fatal(msg ...interface{})

Fatal is a pretty printed fatal line.

func (Collector) Info

func (collector Collector) Info(msg ...interface{})

Info is a pretty printed info line.

func (Collector) OK added in v1.1.6

func (collector Collector) OK() bool

OK takes an advisor interface and uses it to determine the calculated state of the runtime environment. At any time call this to get an advice on whether or not something drastic like a restart, fatal, or panic should be performed. Errnie comes with a default (naive) implementation of an advisor, however the most benefit can be obtained by providing something more custom to your situation.

func (Collector) Panic added in v1.1.6

func (collector Collector) Panic(msg ...interface{})

Panic is a pretty printed panic line.

func (*Collector) Stack

func (collector *Collector) Stack(err error, errType ErrType) *Collector

Stack is a circular buffer that holds a certain amount of recent errors that were collected. The basic concept is to have a real-time sample we can analyze at any time to determine the health of our application. It is therefor recommended to send one errnie down an entire call stack, and the longer you keep him alive, the more useful he is across multiple domains in your code.

func (*Collector) StackDump added in v1.1.6

func (collector *Collector) StackDump(err error, errType ErrType) *Collector

func (*Collector) StackOut added in v1.1.6

func (collector *Collector) StackOut(err error, errType ErrType) *Collector

func (Collector) ToSlice added in v1.1.7

func (collector Collector) ToSlice() []string

func (Collector) Warn added in v1.1.6

func (collector Collector) Warn(msg ...interface{})

Warn is a pretty printed warning line.

type ErrType

type ErrType uint

ErrType defines an enumerable type.

const (
	DEBUG ErrType = iota
	INFO
	WARNING
	ERROR
	CRITICAL
	FATAL
	PANIC
)

Define the enumeration for ErrType.

type Error

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

Error wraps Go's built in error type to extend its functionality with a severity level. This starts to blur the lines between errors and logs, which is somewhat on purpose.

func (Error) Error

func (wrapper Error) Error() error

Error returns the canonical error value as given to us by Go's built in error type. It is mainly used to be able to have a return value that is compatible with idiomatic Go.

type Guard

type Guard struct {
	Err error
	// contains filtered or unexported fields
}

Guard is a way to override any existing interrupt messages such as panics and recover from them either silently or by invoking a custom recovery function. This is very similar to Ruby's rescue method. Similar care should be taken as well. This is made for situations where you have an actual solid recovery plan and it could get stuck in an infinite crash loop if you are not careful. To make this work you have to put in additional systems on top.

func NewGuard

func NewGuard(handler func()) *Guard

NewGuard constructs an instance of a guard that can live basically anywhere and sit ready to either Check or Rescue. Rescue does what it says on the tin and recovers from a panic situation, optionally with a bootstrap function to start the full recovery process. Check I don't remember why it is there, but I will look it up in the original project this all comes from.

func (*Guard) Check

func (guard *Guard) Check()

Check... I have no fucking clue what this does.

func (*Guard) Rescue

func (guard *Guard) Rescue() func()

Rescue a method from errors and panics. The way to set this up is to make a deferred call to a previously instantiated Guard. By deferring the Rescue call no matter what happens the Rescue method will be called and it uses Go's built in recover statement to circumvent the panic.

type RookieAdvisor added in v1.1.6

type RookieAdvisor struct {
}

RookieAdvisor is the built in and most basic implementation of the concept.

func (RookieAdvisor) Dynamic added in v1.1.6

func (advisor RookieAdvisor) Dynamic(<-chan bytes.Buffer) State

Dynamic takes a bytes.Buffer channel so we can send it metadata. The call stack would be an idea for instance. Dynamic advice will also be twice as broad in value scope, which allows for additional complexity.

func (RookieAdvisor) Static added in v1.1.6

func (advisor RookieAdvisor) Static(stack ring.Ring) bool

Static is the entry point for the fast and loose method of determining state.

type State added in v1.1.6

type State uint

State defines an enumerable type of 4 arbitrary values of degradation. Use them however suits you.

const (
	OK State = iota
	NO
	BAD
	DEAD
)

Jump to

Keyboard shortcuts

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