errorv

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

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

Go to latest
Published: Sep 12, 2016 License: MIT Imports: 3 Imported by: 0

README

errorv GoDoc License Build Status Coverage Status GoReportCard

Package errorv provides a simple error API that works well with structured logging.

Many of the ideas, much of the code, and even the text for the documentation of this package is based on the excellent github.com/pkg/errors package.

A key difference between this package and github.com/pkg/errors is that this package has been designed to suit programs that make use of structured logging. Some of the ideas in this package were proposed for package github.com/pkg/errors, but after a reasonable amount of consideration, were ultimately not included in that package.

If you are not using structured logging in your application and have no intention of doing so, use the github.com/pkg/errors package in preference to this one.

Background

The traditional error handling idiom in Go is roughly akin to

if err != nil {
        return err
}

which applied recursively up the call stack results in error reports without context or debugging information. The errorv package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.

Adding context to an error

The errorv.Wrap function returns a new error that adds context to the original error. For example

name := "some-file"
number := 53
err := doSomethingWith(name, number)
if err != nil {
    return errorv.Wrap(err, "cannot do something", 
        "name", name,
        "number", number,
    )
}

Retrieving the cause of an error

Using errorv.Wrap constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errorv.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by errorv.Cause.

type causer interface {
        Cause() error
}

errorv.Cause will recursively retrieve the topmost error which does not implement causer, which is assumed to be the original cause. For example:

switch err := errorv.Cause(err).(type) {
case *MyError:
    // handle specifically
default:
    // unknown error
}

Retrieving key value pairs for structured logging

Errors created by errorv.Wrap and errorv.New implement the following interface.

type keyvalser interface {
	Keyvals() []interface{}
}

The Keyvals method returns an array of alternating keys and values. The first key will always be "msg" and its value will be a string containing the message associated with the wrapped error.

Example using go-kit logging:

// logError logs details of an error to a structured error log.
func logError(logger log.Logger, err error) {
	// start with timestamp and error level
	keyvals := []interface{}{
		"ts",    time.Now().Format(time.RFC3339Nano),
		"level", "error",
	}

	type keyvalser interface {
		Keyvals() []interface{}
	}
	if kv, ok := err.(keyvalser); ok {
		// error contains structured information, first key/value
		// pair will be "msg".
		keyvals = append(keyvals, kv.Keyvals()...)
	} else {
		// error does not contain structured information, use the
		// Error() string as the message.
		keyvals = append(keyvals, "msg", err.Error())
	}
	logger.Log(keyvals...)
}

This interface works well with the github.com/jjeffery/kv package, which provides improved type safety and clarity when working with key value pairs.

GOOD ADVICE: Do not use the Keyvals method on an error to retrieve the individual key/value pairs associated with an error for processing by the calling program.

Read the package documentation for more information.

Licence

MIT

Documentation

Overview

Package errorv provides a simple error API that works well with structured logging.

Many of the ideas, much of the code, and even the text for the documentation of this package is based on the excellent github.com/pkg/errors package (https://github.com/pkg/errors).

A key difference between this package and github.com/pkg/errors is that this package has been designed to suit programs that make use of structured logging. Some of the ideas in this package were proposed for package github.com/pkg/errors, but after a reasonable amount of consideration, were ultimately not included in that package. (See https://github.com/pkg/errors/issues/34 for details).

If you are not using structured logging in your application and have no intention of doing so, use the github.com/pkg/errors package in preference to this one.

Background

The traditional error handling idiom in Go is roughly akin to

if err != nil {
    return err
}

which applied recursively up the call stack results in error reports without context or debugging information. The errorv package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.

Adding context to an error

The errorv.Wrap function returns a new error that adds context to the original error. For example

name := "some-file"
number := 53
err := doSomethingWith(name, number)
if err != nil {
    return errorv.Wrap(err, "cannot do something",
        "name", name,
        "number", number,
    )
}

Retrieving the cause of an error

Using errorv.Wrap constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errorv.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by errorv.Cause.

type causer interface {
    Cause() error
}

errorv.Cause will recursively retrieve the topmost error which does not implement causer, which is assumed to be the original cause. For example:

switch err := errorv.Cause(err).(type) {
case *MyError:
    // handle specifically
default:
    // unknown error
}

Retrieving key value pairs for structured logging

Errors created by `errorv.Wrap` and `errorv.New` implement the following interface:

type keyvalser interface {
    Keyvals() []interface{}
}

The Keyvals method returns an array of alternating keys and values. The first key will always be "msg" and its value will be a string containing the message associated with the wrapped error.

Example using go-kit logging (https://github.com/go-kit/kit/tree/master/log):

// logError logs details of an error to a structured error log.
func logError(logger log.Logger, err error) {
    // start with timestamp and error level
    keyvals := []interface{}{
        "ts",    time.Now().Format(time.RFC3339Nano),
        "level", "error",
    }

    type keyvalser interface {
        Keyvals() []interface{}
    }
    if kv, ok := err.(keyvalser); ok {
        // error contains structured information, first key/value
        // pair will be "msg".
        keyvals = append(keyvals, kv.Keyvals()...)
    } else {
        // error does not contain structured information, use the
        // Error() string as the message.
        keyvals = append(keyvals, "msg", err.Error())
    }
    logger.Log(keyvals...)
}

This interface works well with the github.com/jjeffery/kv package, which provides improved type safety and clarity when working with key value pairs.

GOOD ADVICE: Do not use the `Keyvals` method on an error to retrieve the individual key/value pairs associated with an error for processing by the calling program.

Example
package main

import (
	"fmt"

	"github.com/jjeffery/errorv"
)

func main() {
	err := errorv.New("first error",
		"card", "ace",
		"suite", "spades")
	fmt.Println(err)

	err = errorv.Wrap(err, "second error",
		"piece", "rook",
		"color", "black",
	)
	fmt.Println(err)

}
Output:
first error card=ace suite=spades
second error piece=rook color=black: first error card=ace suite=spades

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Cause

func Cause(err error) error

Cause returns the underlying cause of the error, if possible. An error value has a cause if it implements the following interface:

type causer interface {
       Cause() error
}

If the error does not implement Cause, the original error will be returned. If the error is nil, nil will be returned without further investigation.

Cause is compatible with the Cause function in package "github.com/pkg/errors". The implementation and documentation of Cause has been copied from that package.

Example
package main

import (
	"fmt"

	"github.com/jjeffery/errorv"
)

func main() {
	// tests if an error is a not found error
	type notFounder interface {
		NotFound() bool
	}

	err := getError()

	if notFound, ok := errorv.Cause(err).(notFounder); ok {
		fmt.Printf("Not found: %v", notFound.NotFound())
	}
}

func getError() error {
	return fmt.Errorf("not a not found error")
}

func New

func New(msg string, keyvals ...interface{}) error

New creates a new error.

Example
package main

import (
	"fmt"

	"github.com/jjeffery/errorv"
)

func getNameOfThing() string {
	return "!not-valid"
}

func isValidName(name string) bool {
	return false
}

func main() {
	name := getNameOfThing()

	if !isValidName(name) {
		fmt.Println(errorv.New("invalid name", "name", name))
	}
}
Output:
invalid name name=!not-valid

func Wrap

func Wrap(err error, msg string, keyvals ...interface{}) error

Wrap creates an error that wraps an existing error, optionally providing additional information. If err is nil, Wrap returns nil.

Types

type Context

type Context interface {
	New(msg string, keyvals ...interface{}) error
	Wrap(err error, msg string, keyvals ...interface{}) error
	NewContext(keyvals ...interface{}) Context
}

A Context can be useful for specifying common key/value pairs that will be attached to all error messages created from that context.

Example
package main

import (
	"fmt"

	"github.com/jjeffery/errorv"
)

var userID = "u1"
var documentID = "d1"

func main() {
	// ... if a function has been called with userID and DocumentID ...
	errorv := errorv.NewContext("userID", userID, "documentID", documentID)

	n, err := doOneThing()
	if err != nil {
		// will include key value pairs for userID and document ID
		fmt.Println(errorv.Wrap(err, "cannot do one thing"))
	}

	if err := doAnotherThing(n); err != nil {
		// will include key value pairs for userID, document ID and n
		fmt.Println(errorv.Wrap(err, "cannot do another thing", "n", n))
	}

}

func doOneThing() (int, error) {
	return 0, fmt.Errorf("doOneThing: unable to finish")
}

func doAnotherThing(n int) error {
	return fmt.Errorf("doAnotherThing: not working properly")
}
Output:
cannot do one thing userID=u1 documentID=d1: doOneThing: unable to finish
cannot do another thing userID=u1 documentID=d1 n=0: doAnotherThing: not working properly

func NewContext

func NewContext(keyvals ...interface{}) Context

NewContext creates a new error context with information that will be associated with any errors created from that context.

Jump to

Keyboard shortcuts

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