errors

package module
v1.40.0 Latest Latest
Warning

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

Go to latest
Published: Jan 9, 2025 License: BSD-2-Clause Imports: 9 Imported by: 6

README

errors

  • stack traces for errors
  • a more structured API for error wrapping.

There is a blog article overview here.

Adding stack traces and context to an error

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

_, err := ioutil.ReadAll(r)
if err != nil {
        return errors.Wrap(err, "read failed")
}

There are a few other functions available such as Wrapf (use a format string) and New and Errorf (create a new error).

These all add a stack trace to errors. So generally wherever an error is generated in your code, you want to wrap it to add a stack trace. If you don't want to bother with adding any additional metadata, you can just use AddStack.

You can also add context with slog attributes with Wraps

return errors.Wraps(err, "failure", "id", 5, "key", "value)

Using with standard library errors

This library is designed to be used in place of the standard errors package. There are cases where stack traces are not desired. The suggested pattern is to rename errors to stderrors when importing.

import (
        stderrors "errors"
        "github.com/gregwebs/errors"
)

var errVar = stderrors.New("stack trace not desired")

Retrieving the cause of an error

Using errors.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 errors.Wrap to retrieve the original error for inspection. Any error value which implements the method Unwrap can be inspected by standard errors package functions or by errors.Cause.

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

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

License

BSD-2-Clause

Documentation

Overview

Package errors provides error handling primitives that add stack traces and metadata to errors.

Key Concepts:

  • Adding Stack traces: All the error creation and wrapping functions ensure a stack trace is recorded for the error.
  • Adding Context: The `errors.Wrap` and `errors.Wrapf` functions adds an additional string context to an error.
  • Adding Structured data: The `errors.Wraps` and `errors.Slog` functions adds structured data to errors.
  • Formatted Printing: Errors returned from this package implement the `fmt.Formatter` interface- verbose printing options will show the stack trace.
  • Retrieving underlying errors: In addition to standard `errors.Unwrap`, `errors.Is`, and `errors.As`, there are `errors.AsType`, `errors.Cause`, and `errors.UnwrapGroups`.
  • Retrieving the Stack Trace: `errors.GetStackTracer` retrieves the stack trace from the error.
  • Retrieving the structured data: `errors.SlogRecord` retrieves structured data as an slog.Record.

Formatted printing of errors

All error values returned from this package implement fmt.Formatter and can be formatted by the fmt package. The following verbs are supported

%s    print the error. If the error has a Cause it will be
      printed recursively with colon separations
%v    see %s
%+v   extended format. Each Frame of the error's StackTrace will
      be printed in detail.
%-v   similar to %s but newline separated. No stack traces included.
Example (StackTrace)
package main

import (
	"fmt"
	"strings"

	"github.com/gregwebs/errors"
	"github.com/gregwebs/stackfmt"
)

func functionLines(s string) string {
	lines := []string{}
	for _, line := range strings.SplitAfter(s, "\n") {

		if strings.HasPrefix(line, "github.com") {
			lines = append(lines, line)

		} else if strings.HasPrefix(line, "\t") || len(lines) == 0 {
			continue
		} else {
			break
		}
	}
	return strings.Join(lines, "")
}

func newWrappedErr() error {
	e1 := errors.New("cause")
	e2 := errors.Wrap(e1, "inner")
	e3 := errors.Wrap(e2, "middle")
	return errors.Wrap(e3, "outer")
}

func main() {
	type stackTracer interface {
		StackTrace() stackfmt.StackTrace
	}

	err, ok := errors.Cause(newWrappedErr()).(stackTracer)
	if !ok {
		panic("oops, err does not implement stackTracer")
	}

	st := err.StackTrace()
	fmt.Print(functionLines(fmt.Sprintf("%+v", st)))
}
Output:

github.com/gregwebs/errors_test.newWrappedErr
github.com/gregwebs/errors_test.Example_stackTrace

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddStack added in v0.12.0

func AddStack(err error) error

AddStack annotates err with a stack trace at the point WithStack was called. It will first check with HasStack to see if a stack trace already exists before creating another one.

Example
package main

import (
	stderrors "errors"
	"fmt"
	"strings"

	"github.com/gregwebs/errors"
)

// Returns the first 'n' lines of a given string, where each line is separated by '\n'.
func firstNLines(s string, n int) string {
	allLines := strings.SplitN(s, "\n", n+1)
	if n > len(allLines) {
		n = len(allLines)
	}
	return strings.Join(allLines[0:n], "\n")
}

func main() {
	// stderrors is the standard library errors: no stack trace
	cause := stderrors.New("whoops")
	fmt.Print(firstNLines(fmt.Sprintf("%+v\n", cause), 2))
	// Add a stack trace to it
	err := errors.AddStack(cause)
	fmt.Print(firstNLines(fmt.Sprintf("%+v\n", err), 2))
}
Output:

whoops
whoops
github.com/gregwebs/errors_test.ExampleAddStack

func AddStackSkip added in v1.1.0

func AddStackSkip(err error, skip int) error

Same as AddStack but specify an additional number of callers to skip

Example
package main

import (
	stderrors "errors"
	"fmt"
	"strings"

	"github.com/gregwebs/errors"
)

func functionLines(s string) string {
	lines := []string{}
	for _, line := range strings.SplitAfter(s, "\n") {

		if strings.HasPrefix(line, "github.com") {
			lines = append(lines, line)

		} else if strings.HasPrefix(line, "\t") || len(lines) == 0 {
			continue
		} else {
			break
		}
	}
	return strings.Join(lines, "")
}

func main() {
	// stderrors is the standard library errors: no stack trace
	inner := func() {
		cause := stderrors.New("whoops")
		err := errors.AddStack(cause)
		fmt.Print(functionLines(fmt.Sprintf("%+v\n", err)))

		fmt.Println("---")

		// Add a stack trace to it
		err = errors.AddStackSkip(cause, 1)
		fmt.Print(functionLines(fmt.Sprintf("%+v\n", err)))
	}
	inner()
}
Output:

github.com/gregwebs/errors_test.ExampleAddStackSkip.func1
github.com/gregwebs/errors_test.ExampleAddStackSkip
---
github.com/gregwebs/errors_test.ExampleAddStackSkip

func As added in v1.0.0

func As(err error, target any) bool

A re-export of the standard library errors.As

func AsType added in v1.11.0

func AsType[Err error](err error) (Err, bool)

AsType is equivalient to As and returns the same boolean. Instead of instantiating a struct and passing it by pointer, the type of the error is given as the generic argument It is instantiated and returned.

Example
package main

import (
	"fmt"

	"github.com/gregwebs/errors"
)

type ErrEmpty struct{}

func (et ErrEmpty) Error() string {
	return "empty"
}

func main() {
	err := errors.Wrap(ErrEmpty{}, "failed")
	cause, _ := errors.AsType[ErrEmpty](err)
	fmt.Printf("%v", cause)
}
Output:

empty

func Cause

func Cause(err error) error

Cause returns the underlying cause of the error, if possible. Unwrap goes just one level deep, but Cause goes all the way to the bottom If nil is given, it will return nil

Example
package main

import (
	"fmt"

	"github.com/gregwebs/errors"
)

func newWrappedErr() error {
	e1 := errors.New("cause")
	e2 := errors.Wrap(e1, "inner")
	e3 := errors.Wrap(e2, "middle")
	return errors.Wrap(e3, "outer")
}

func main() {
	err := newWrappedErr()
	fmt.Println(err)
	fmt.Println(errors.Cause(err))
}
Output:

outer: middle: inner: cause
cause

func Errorf added in v0.3.0

func Errorf(format string, args ...interface{}) error

Errorf formats according to a format specifier and returns the string as a value that satisfies error. Errorf also records the stack trace at the point it was called.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/gregwebs/errors"
)

func main() {
	err := errors.Errorf("whoops: %s", "foo")
	verbose := fmt.Sprintf("%+v", err)
	fmt.Print(strings.Join(strings.SplitN(verbose, "\n", 3)[0:2], "\n"))
}
Output:

whoops: foo
github.com/gregwebs/errors_test.ExampleErrorf

func GetStackTracer added in v0.12.0

func GetStackTracer(origErr error) stackfmt.StackTracer

GetStackTracer will return the first StackTracer in the causer chain. This function is used by AddStack to avoid creating redundant stack traces.

You can also use the StackTracer interface on the returned error to get the stack trace.

func HandleFmtWriteError added in v1.30.0

func HandleFmtWriteError(handler func(err error))

HandleFmtWriteError handles (rare) errors when writing to fmt.State. It defaults to printing the errors.

func HasStack added in v0.12.0

func HasStack(err error) bool

HasStack returns true if the error will find a stack trace It does not unwrap errors It looks for stackfmt.StackTracer, stackfmt.StackTraceFormatter, or the method HasStack() bool

func Is added in v1.0.0

func Is(err, target error) bool

A re-export of the standard library errors.Is

func IsNil added in v1.21.0

func IsNil(err error) bool

IsNil performs additional checks besides == nil This helps deal with a design issue with Go interfaces: https://go.dev/doc/faq#nil_error It will return true if the error interface contains a nil pointer, interface, slice, array or map It will return true if the slice or array or map is empty

Example
package main

import (
	"fmt"

	"github.com/gregwebs/errors"
)

type ErrEmpty struct{}

func (et ErrEmpty) Error() string {
	return "empty"
}

func main() {
	var empty *ErrEmpty = nil //nolint:staticcheck
	var err error = empty
	fmt.Println(err == nil) //nolint:staticcheck
	fmt.Println(errors.IsNil(err))
}
Output:

false
true

func Join added in v1.14.0

func Join(errs ...error) error

The same as the standard errors.Join

func Joins added in v1.14.0

func Joins(errs ...error) []error

The same as errors.Join but returns the array rather than wrapping it. Also uses isNil for a better nil check.

func New

func New(message string) error

New returns an error with the supplied message. New also records the stack trace at the point it was called.

Example
package main

import (
	"fmt"

	"github.com/gregwebs/errors"
)

func main() {
	err := errors.New("whoops")
	fmt.Println(err)
}
Output:

whoops

func Slog added in v1.7.0

func Slog(msg string, args ...interface{}) slogerr.StructuredError

Slog creates an error that instead of generating a format string generates a structured slog Record. Accepts as args any valid slog args. Also accepts `[]slog.Attr` as a single argument to avoid having to cast that argument. The slog Record can be retrieved with SlogRecord. Structured errors are more often created by wrapping existing errors with Wraps.

func SlogRecord added in v1.2.0

func SlogRecord(inputErr error) *slog.Record

SlogRecord traverses the error chain, calling Unwrap(), to look for slog Records All records will be merged and mesage strings are joined together The message string may contain some of the structured information This depends on defining ErrorNoUnwrap from the interface ErrorNotUnwrapped

if record := errors.SlogRecord(errIn); record != nil {
	record.Add(logArgs...)
	if err := slog.Default().Handler().Handle(ctx, *record); err != nil {
		slog.ErrorContext(ctx, fmt.Sprintf("%+v", err))
	}
}

func Unwrap added in v0.12.0

func Unwrap(err error) error

Unwrap uses the Unwrap method to return the next error in the chain or nil. This is the same as the standard errors.Unwrap

func Unwraps added in v1.30.0

func Unwraps(err error) []error

func Wrap

func Wrap(err error, message string) error

Wrap returns an error annotating err with a stack trace at the point Wrap is called, and the supplied message. If err is nil, Wrap returns nil.

Example
package main

import (
	"fmt"

	"github.com/gregwebs/errors"
)

func main() {
	cause := errors.New("whoops")
	err := errors.Wrap(cause, "oh noes")
	fmt.Println(err)
}
Output:

oh noes: whoops

func WrapFn added in v1.13.0

func WrapFn(msg string) func(error) error

WrapFn returns a wrapping function that calls Wrap

func WrapNoStack added in v1.21.0

func WrapNoStack(err error, message string) error

WrapNoStack does not add a new stack trace. WrapNoStack annotates err with a new message. If err is nil, returns nil. When used consecutively, it will append the message strings rather than creating a new error

Example
package main

import (
	stderrors "errors"
	"fmt"

	"github.com/gregwebs/errors"
)

func main() {
	cause := stderrors.New("whoops")
	err := errors.WrapNoStack(cause, "oh noes")
	fmt.Printf("%+v", err)
}
Output:

whoops
oh noes

func Wrapf added in v0.2.0

func Wrapf(err error, format string, args ...interface{}) error

Wrapf returns an error annotating err with a stack trace at the point Wrapf is call, and the format specifier. If err is nil, Wrapf returns nil.

Example
package main

import (
	"fmt"

	"github.com/gregwebs/errors"
)

func main() {
	cause := errors.New("whoops")
	err := errors.Wrapf(cause, "oh noes #%d", 2)
	fmt.Println(err)
}
Output:

oh noes #2: whoops

func WrapfFn added in v1.13.0

func WrapfFn(msg string, args ...interface{}) func(error) error

WrapfFn returns a wrapping function that calls Wrapf

func Wraps added in v1.2.0

func Wraps(err error, msg string, args ...interface{}) slogerr.StructuredError

Wraps ends with an "s" to indicate it is Structured. Accepts as args any valid slog args. These will generate an slog Record Also accepts []slog.Attr as a single argument to avoid having to cast that argument.

Types

This section is empty.

Directories

Path Synopsis
errwrap module
slogerr module

Jump to

Keyboard shortcuts

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