errors

package module
v0.0.0-...-a723955 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2020 License: Apache-2.0 Imports: 6 Imported by: 0

README

Travis CI build status

github.com/chaimleib/errors

https://godoc.org/github.com/chaimleib/errors

Doctor Gopher treating patient

This Go package is a drop-in replacement to the built-in errors package. It is designed for Go 1.13, while under 1.12, the Is, As and Unwrap functions have been backported.

Why?

Error messages exist to help you fix the problem. I kept finding that regular error messages required too much searching to understand the issue. I like this better:

main.main() main.go:34 error getting user profile
~/client.UserProfile(ctx, "alice") userprofile.go:124 error authenticating
~/client.Authenticate(ctx, "bob", pw) client.go:63 password expired

Your go.mod main module name is abbreviated as ~.

That's much more helpful than this:

password expired

What operation required a password? Where did this get generated? How did we get there?

With enhanced errors, you can begin fixing the problem right away:

  1. You know exactly where the error came from.
  2. You know what code path led to the error.
  3. You know what inputs each function was seeing.

All this, without having to use rg or open up any files!

How?

  1. At the beginning of your methods, have this:
func (client Client) Authenticate(ctx context.Context, user, pw string) (UserProfile, error) {
  b := errors.NewBuilder("ctx, %q, pw", user)
  // Note: You don't have to show values for all the arguments.
  // An idea: for brevity, try "[%d]aSlice", len(aSlice) instead of showing the
  // whole slice or map, etc.
  1. Whenever you return an error:
if err != nil {
  return nil, b.Wrap(err, "error parsing %q", value)
}
  1. At the top of the program, print the full stack trace:
if err != nil {
  fmt.Println(errors.StackString(err))
}

Another example (try me!):

func main() {
	b := errors.NewBuilder("")
	if err := FileHasHello("greet.txt"); err != nil {
		err = b.Wrap(err, "program failed")
		fmt.Println(errors.StackString(err))
		return
	}
}

func FileHasHello(fpath string) error {
	b := errors.NewBuilder("%q", fpath)
	buf, err := ioutil.ReadFile(fpath)
	if err != nil {
		return b.Wrap(err, "could not open file")
	}
	if !bytes.Contains(buf, []byte("hello")) {
		return b.Errorf("could not find `hello` in file")
	}
	return nil
}

/* output:
main.main() prog.go:14 program failed
main.FileHasHello("greet.txt") prog.go:24 could not open file
open greet.txt: No such file or directory
No such file or directory
*/

What else can I do?

  • Print just one level of stack trace using StackStringAt(err). Index into Stack(err) to select an error by its Unwrap() depth.

  • Use Is, As and Unwrap in Go 1.12 (added officially in Go 1.13)

  • Group errors (try me)

errs := []error{
  fmt.Errorf("server A failed"),
  fmt.Errorf("server B failed"),
}
wrapped := errors.Wrap(errors.Group(errs), "all servers failed")
fmt.Println(errors.StackString(wrapped))
// all servers failed
// [
//     server A failed
//     ,
//     server B failed
// ]
  • Get call site info with NewFuncInfo(calldepth). (This is for debugging output only. It is bad design to write application logic around these values.)
fi := errors.NewFuncInfo(1)
fmt.Println(fi.File(), fi.Line(), fi.FuncName())
// /absolute/path/to/main.go 12 main.main
  • Customize your stack trace formatting by writing your our StackString() function. All the necessary plumbing (like FuncInfo(), ArgStringer() and Wrapper()) is exposed as public methods.

License

Copyright 2019-2020 Chaim Leib Halbert

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this source code 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

Index

Constants

This section is empty.

Variables

View Source
var (
	// As finds the first error in err's chain that matches target, and if so,
	// sets target to that error value and returns true.
	//
	// 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(interface{})
	// bool such that As(target) returns true. In the latter case, the As method
	// is responsible for setting target.
	//
	// As will panic if target is not a non-nil pointer to either a type that
	// implements error, or to any interface type. As returns false if err is
	// nil.
	As = errors.As

	// Is reports whether any error in err's chain matches target.
	//
	// The chain consists of err itself followed by the sequence of errors
	// obtained by repeatedly calling Unwrap.
	//
	// An error is considered to match a target if it is equal to that target or
	// if it implements a method Is(error) bool such that Is(target) returns
	// true.
	Is = errors.Is

	// 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.
	New = errors.New

	// 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.
	Unwrap = errors.Unwrap
)

Functions

func MainModule

func MainModule() string

MainModule returns the path of the currently-running binary's main module, as defined in the go.mod file. If not built with module support, returns "".

func RelativeModule

func RelativeModule(modName, home string) string

RelativeModule replaces occurrences of `home` inside `modName` with a tilde "~". If `home` is the empty string or if `modName` is not a child of home, just return `modName` without changing it.

func Stack

func Stack(err error) []error

Stack returns a slice of all the errors found by recursively calling Unwrap() on the provided error. Errors causing other errors appear later.

func StackString

func StackString(err error) string

StackString recursively calls Unwrap() on the given error and stringifies all the errors in the chain.

The stringification adds location prefixes to errors that additionally implement `FuncInfo() FuncInfo` and optionally `ArgStringer() interface{ String() string }`.

The stringification can be overridden if the error implements `StackString() string`.

func StackStringAt

func StackStringAt(err error) string

StackStringAt returns one level of StackString.

Types

type Builder

type Builder interface {
	Errorf(msg string, args ...interface{}) error
	Wrap(err error, msg string, args ...interface{}) Wrapped
}

Builder implementors can make and wrap errors.

var BuiltinBuilder Builder = (*builtinBuilder)(nil)

BuiltinBuilder has no frills. It is a proxy to built-in go packages.

func NewBuilder

func NewBuilder(argFmt string, args ...interface{}) Builder

NewBuilder returns an error builder that attaches info about the function where the error happened, and the args with which the function was called.

func NewLazyBuilder

func NewLazyBuilder(argFmt string, args ...interface{}) Builder

NewLazyBuilder SHOULD NOT be used unless it is known that NewBuilder won't work. Frequent undisciplined usage of NewLazyBuilder can lead to poor code maintainability. It is similar to NewBuilder, except that the embedded ArgStringer()s do their formatting work lazily when the error is formatted for display, rather than up-front when the Builder is initialized. This can be important for performance in functions called thousands of times per second, but misleading debug messages can result if the arguments have changed since the function was first called. As a debug warning, any args are labeled "<lazy>" by the ArgStringer().

type FuncInfo

type FuncInfo interface {
	File() string
	Line() int
	FuncName() string
}

FuncInfo identifies a line of code, as provided by go runtime package functions.

func NewFuncInfo

func NewFuncInfo(calldepth int) FuncInfo

NewFuncInfo ascends the number of stack frames indicated by calldepth and returns a FuncInfo describing the location of that line of code. A calldepth of 0 refers to the invocation of NewFunInfo itself.

type Group

type Group []error

Group allows treating a slice of errors as an error. This is useful when many errors together lead to one error downstream. For example, a network fetch might fail only if all the mirrors fail to respond.

func (Group) Error

func (g Group) Error() string

Error formats a []error as a list of errors if len() is 2 or more, otherwise as a single error if len() is 1, otherwise as nothing if len() is 0.

func (Group) StackString

func (g Group) StackString() string

StackString formats a []error as a recursive list of StackString-ed errors if len() is 2 or more, otherwise as a single StackString-ed error if len() is 1, otherwise as nothing if len() is 0.

type Wrapped

type Wrapped interface {
	error
	Unwrap() error
}

Wrapped is an error whose cause can be retrieved with Unwrap()

func Wrap

func Wrap(cause error, msg string, vars ...interface{}) Wrapped

Wrap is like fmt.Errorf, except that calling Unwrap() on the result yields the provided cause.

func WrapWith

func WrapWith(cause, err error) Wrapped

WrapWith takes two errors and wraps the first provided error with the second. This is particularly useful when the wrapping error itself is a special type with custom fields and methods.

Jump to

Keyboard shortcuts

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