eris: github.com/rotisserie/eris Index | Examples | Files

package eris

import "github.com/rotisserie/eris"

Package eris provides a better way to handle, trace, and log errors in Go.

Named after the Greek goddess of strife and discord, this package is designed to give you more control over error handling via error wrapping, stack tracing, and output formatting. eris was inspired by a simple question: what if you could fix a bug without wasting time replicating the issue or digging through the code?

Many of the methods in this package will look familiar if you've used pkg/errors or xerrors, but eris employs some additional tricks during error wrapping and unwrapping that greatly improve the readability of the stack which should make debugging easier. This package also takes a unique approach to formatting errors that allows you to write custom formats that conform to your error or log aggregator of choice.

Creating errors

Creating errors is simple via eris.New and eris.NewGlobal.

var (
  // global error values can be useful when wrapping errors or inspecting error types
  ErrInternalServer = eris.NewGlobal("error internal server")
)

func (req *Request) Validate() error {
  if req.ID == "" {
    // or return a new error at the source if you prefer
    return eris.New("error bad request")
  }
  return nil
}

Wrapping errors

eris.Wrap adds context to an error while preserving the original error.

relPath, err := GetRelPath("/Users/roti/", resource.AbsPath)
if err != nil {
  // wrap the error if you want to add more context
  return nil, eris.Wrapf(err, "failed to get relative path for resource '%v'", resource.ID)
}

Formatting and logging errors

eris.ToString and eris.ToJSON should be used to log errors with the default format. The JSON method returns a map[string]interface{} type for compatibility with Go's encoding/json package and many common JSON loggers (e.g. logrus).

// format the error to JSON with the default format and stack traces enabled
formattedJSON := eris.ToJSON(err, true)
fmt.Println(json.Marshal(formattedJSON)) // marshal to JSON and print
logger.WithField("error", formattedJSON).Error() // or ideally, pass it directly to a logger

// format the error to a string and print it
formattedStr := eris.ToString(err, true)
fmt.Println(formattedStr)

eris also enables control over the default format's separators and allows advanced users to write their own custom formats.

Interpreting eris stack traces

Errors created with this package contain stack traces that are managed automatically. They're currently mandatory when creating and wrapping errors but optional when printing or logging. The stack trace and all wrapped layers follow the same order as Go's `runtime` package, which means that the root cause of the error is shown first.

{
  "root":{
    "message":"error bad request", // root cause
    "stack":[
      "eris_test.(*Request).Validate:.../example_logger_test.go:25", // location of the root
      "eris_test.(*Request).Validate:.../example_logger_test.go:26", // location of Wrap call
      "eris_test.ProcessResource:.../example_logger_test.go:68",
      "eris_test.Example_logger:.../example_logger_test.go:140",
    ]
  },
  "wrap":[
    {
      "message":"received a request with no ID", // additional context
      "stack":"eris_test.(*Request).Validate:.../example_logger_test.go:26" // location of Wrap call
    }
  ]
}

Inspecting errors

The eris package provides a couple ways to inspect and compare error types. eris.Is returns true if a particular error appears anywhere in the error chain. Currently, it works simply by comparing error messages with each other. If an error contains a particular message (e.g. "error not found") anywhere in its chain, it's defined to be that error type.

ErrNotFound := eris.NewGlobal("error not found")
_, err := db.Get(id)
// check if the resource was not found
if eris.Is(err, ErrNotFound) {
  // return the error with some useful context
  return eris.Wrapf(err, "error getting resource '%v'", id)
}

eris.Cause unwraps an error until it reaches the cause, which is defined as the first (i.e. root) error in the chain.

ErrNotFound := eris.NewGlobal("error not found")
_, err := db.Get(id)
// compare the cause to some sentinel value
if eris.Cause(err) == ErrNotFound {
  // return the error with some useful context
  return eris.Wrapf(err, "error getting resource '%v'", id)
}

Index

Examples

Package Files

doc.go eris.go format.go stack.go

func Cause Uses

func Cause(err error) error

Cause returns the root cause of the error, which is defined as the first error in the chain. The original error is returned if it does not implement `Unwrap() error` and nil is returned if the error is nil.

func Errorf Uses

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

Errorf creates a new root error with a formatted message.

func Is Uses

func Is(err, target error) bool

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.

func New Uses

func New(msg string) error

New creates a new root error with a static message.

func NewGlobal Uses

func NewGlobal(msg string) error

NewGlobal creates a new root error for use as a global sentinel type.

func StackFrames Uses

func StackFrames(e error) []uintptr

StackFrames returns the trace of an error in the form of a program counter slice. Use this method if you want to pass the eris stack trace to some other error tracing library.

func ToCustomJSON Uses

func ToCustomJSON(err error, format Format) map[string]interface{}

ToCustomJSON returns a JSON formatted map for a given eris error.

To declare custom format, the Format object has to be passed as an argument. An error without trace will be formatted as following:

{
  "root": {
    "message": "Root error msg",
  },
  "wrap": [
    {
      "message": "Wrap error msg'",
    }
  ]
}

An error with trace will be formatted as following:

{
  "root": {
    "message": "Root error msg",
    "stack": [
      "<Method1>[Format.StackElemSep]<File1>[Format.StackElemSep]<Line1>",
      "<Method2>[Format.StackElemSep]<File2>[Format.StackElemSep]<Line2>"
    ]
  }
  "wrap": [
    {
      "message": "Wrap error msg",
      "stack": "<Method2>[Format.StackElemSep]<File2>[Format.StackElemSep]<Line2>"
    }
  ]
}

func ToCustomString Uses

func ToCustomString(err error, format Format) string

ToCustomString returns a custom formatted string for a given eris error.

To declare custom format, the Format object has to be passed as an argument. An error without trace will be formatted as following:

<Root error msg>[Format.ErrorSep]<Wrap error msg>

An error with trace will be formatted as following:

<Root error msg>[Format.MsgStackSep]
[Format.PreStackSep]<Method1>[Format.StackElemSep]<File1>[Format.StackElemSep]<Line1>[Format.ErrorSep]
[Format.PreStackSep]<Method2>[Format.StackElemSep]<File2>[Format.StackElemSep]<Line2>[Format.ErrorSep]
<Wrap error msg>[Format.MsgStackSep]
[Format.PreStackSep]<Method2>[Format.StackElemSep]<File2>[Format.StackElemSep]<Line2>[Format.ErrorSep]

func ToJSON Uses

func ToJSON(err error, withTrace bool) map[string]interface{}

ToJSON returns a JSON formatted map for a given eris error.

An error without trace will be formatted as following:

{
  "root": [
    {
      "message": "Root error msg"
    }
  ],
  "wrap": {
    "message": "Wrap error msg"
  }
}

An error with trace will be formatted as following:

{
  "root": [
    {
      "message": "Root error msg",
      "stack": [
        "<Method1>:<File1>:<Line1>",
        "<Method2>:<File2>:<Line2>"
      ]
    }
  ],
  "wrap": {
    "message": "Wrap error msg",
    "stack": "<Method2>:<File2>:<Line2>"
  }
}

Demonstrates JSON formatting of wrapped errors that originate from external (non-eris) error types. You can try this example in the Go playground (https://play.golang.org/p/29yCByzK8wT).

Code:

// example func that returns an IO error
readFile := func(fname string) error {
    return io.ErrUnexpectedEOF
}

// unpack and print the error
err := readFile("example.json")
u, _ := json.Marshal(eris.ToJSON(err, false)) // false: omit stack trace
fmt.Println(string(u))

// example output:
// {
//   "external":"unexpected EOF"
// }

Demonstrates JSON formatting of wrapped errors that originate from global root errors (created via eris.NewGlobal). You can try this example in the Go playground (https://play.golang.org/p/jkZHLfHsYHV).

Code:

// declare a "global" error type
ErrUnexpectedEOF := eris.NewGlobal("unexpected EOF")

// example func that wraps a global error value
readFile := func(fname string) error {
    return eris.Wrapf(ErrUnexpectedEOF, "error reading file '%v'", fname) // line 6
}

// example func that catches and returns an error without modification
parseFile := func(fname string) error {
    // read the file
    err := readFile(fname) // line 12
    if err != nil {
        return err
    }
    return nil
}

// unpack and print the error via uerr.ToJSON(...)
err := parseFile("example.json")                             // line 20
u, _ := json.MarshalIndent(eris.ToJSON(err, true), "", "\t") // true: include stack trace
fmt.Printf("%v\n", string(u))

// example output:
// {
//   "root": {
//     "message": "unexpected EOF",
//     "stack": [
//       "main.readFile:.../example/main.go:6",
//       "main.parseFile:.../example/main.go:12",
//       "main.main:.../example/main.go:20",
//     ]
//   },
//   "wrap": [
//     {
//       "message": "error reading file 'example.json'",
//       "stack": "main.readFile:.../example/main.go:6"
//     }
//   ]
// }

Demonstrates JSON formatting of wrapped errors that originate from local root errors (created at the source of the error via eris.New). You can try this example in the Go playground (https://play.golang.org/p/66nsuoOgQWu).

Code:

// example func that returns an eris error
readFile := func(fname string) error {
    return eris.New("unexpected EOF") // line 3
}

// example func that catches an error and wraps it with additional context
parseFile := func(fname string) error {
    // read the file
    err := readFile(fname) // line 9
    if err != nil {
        return eris.Wrapf(err, "error reading file '%v'", fname) // line 11
    }
    return nil
}

// example func that just catches and returns an error
processFile := func(fname string) error {
    // parse the file
    err := parseFile(fname) // line 19
    if err != nil {
        return err
    }
    return nil
}

// another example func that catches and wraps an error
printFile := func(fname string) error {
    // process the file
    err := processFile(fname) // line 29
    if err != nil {
        return eris.Wrapf(err, "error printing file '%v'", fname) // line 31
    }
    return nil
}

// unpack and print the raw error
err := printFile("example.json") // line 37
u, _ := json.MarshalIndent(eris.ToJSON(err, true), "", "\t")
fmt.Printf("%v\n", string(u))

// example output:
// {
//   "root": {
//     "message": "unexpected EOF",
//     "stack": [
//       "main.readFile:.../example/main.go:3",
//       "main.parseFile:.../example/main.go:9",
//       "main.parseFile:.../example/main.go:11",
//       "main.processFile:.../example/main.go:19",
//       "main.printFile:.../example/main.go:29",
//       "main.printFile:.../example/main.go:31",
//       "main.main:.../example/main.go:37",
//     ]
//   },
//   "wrap": [
//     {
//       "message": "error reading file 'example.json'",
//       "stack": "main.parseFile: .../example/main.go: 11"
//     },
//     {
//       "message": "error printing file 'example.json'",
//       "stack": "main.printFile:.../example/main.go:31"
//     }
//   ]
// }

func ToString Uses

func ToString(err error, withTrace bool) string

ToString returns a default formatted string for a given eris error.

An error without trace will be formatted as following:

<Root error msg>: <Wrap error msg>

An error with trace will be formatted as following:

<Root error msg>
  <Method1>:<File1>:<Line1>
  <Method2>:<File2>:<Line2>
<Wrap error msg>
  <Method2>:<File2>:<Line2>

Demonstrates string formatting of wrapped errors that originate from external (non-eris) error types. You can try this example in the Go playground (https://play.golang.org/p/OKbU3gzIZvZ).

Code:

// example func that returns an IO error
readFile := func(fname string) error {
    return io.ErrUnexpectedEOF
}

// unpack and print the error
err := readFile("example.json")
fmt.Println(eris.ToString(err, false)) // false: omit stack trace

// example output:
// unexpected EOF

Demonstrates string formatting of wrapped errors that originate from global root errors (created via eris.NewGlobal). You can try this example in the Go playground (https://play.golang.org/p/8YgyDwk9xBJ).

Code:

// declare a "global" error type
ErrUnexpectedEOF := eris.NewGlobal("unexpected EOF")

// example func that wraps a global error value
readFile := func(fname string) error {
    return eris.Wrapf(ErrUnexpectedEOF, "error reading file '%v'", fname) // line 6
}

// example func that catches and returns an error without modification
parseFile := func(fname string) error {
    // read the file
    err := readFile(fname) // line 12
    if err != nil {
        return err
    }
    return nil
}

// call parseFile and catch the error
err := parseFile("example.json") // line 20

// print the error via fmt.Printf
fmt.Printf("%v\n", err) // %v: omit stack trace

// example output:
// unexpected EOF: error reading file 'example.json'

// unpack and print the error via uerr.ToString(...)
fmt.Printf("%v\n", eris.ToString(err, true)) // true: include stack trace

// example output:
// unexpected EOF
//   main.readFile:.../example/main.go:6
//   main.parseFile:.../example/main.go:12
//   main.main:.../example/main.go:20
// error reading file 'example.json'
//   main.readFile:.../example/main.go:6

Demonstrates string formatting of wrapped errors that originate from local root errors (created at the source of the error via eris.New). You can try this example in the Go playground (https://play.golang.org/p/d49gTNx3OtA).

Code:

// example func that returns an eris error
readFile := func(fname string) error {
    return eris.New("unexpected EOF") // line 3
}

// example func that catches an error and wraps it with additional context
parseFile := func(fname string) error {
    // read the file
    err := readFile(fname) // line 9
    if err != nil {
        return eris.Wrapf(err, "error reading file '%v'", fname) // line 11
    }
    return nil
}

// call parseFile and catch the error
err := parseFile("example.json") // line 17

// print the error via fmt.Printf
fmt.Printf("%v\n", err) // %v: omit stack trace

// example output:
// unexpected EOF: error reading file 'example.json'

// unpack and print the error via uerr.ToString(...)
fmt.Println(eris.ToString(err, true)) // true: include stack trace

// example output:
// unexpected EOF
//   main.readFile:.../example/main.go:3
//   main.parseFile:.../example/main.go:9
//   main.parseFile:.../example/main.go:11
//   main.main:.../example/main.go:17
// error reading file 'example.json'
//   main.parseFile:.../example/main.go:11

func Unwrap Uses

func Unwrap(err error) error

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.

func Wrap Uses

func Wrap(err error, msg string) error

Wrap adds additional context to all error types while maintaining the type of the original error.

This method behaves differently for each error type. For root errors, the stack trace is reset to the current callers which ensures traces are correct when using global/sentinel error values. Wrapped error types are simply wrapped with the new context. For external types (i.e. something other than root or wrap errors), a new root error is created for the original error and then it's wrapped with the additional context.

func Wrapf Uses

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

Wrapf adds additional context to all error types while maintaining the type of the original error.

This is a convenience method for wrapping errors with formatted messages and is otherwise the same as Wrap.

type ErrLink struct {
    Msg   string
    Frame StackFrame
}

ErrLink represents a single error frame and the accompanying message.

type ErrRoot Uses

type ErrRoot struct {
    Msg   string
    Stack Stack
}

ErrRoot represents an error stack and the accompanying message.

type Format Uses

type Format struct {
    WithTrace    bool   // Flag that enables stack trace output.
    MsgStackSep  string // Separator between error messages and stack frame data.
    PreStackSep  string // Separator at the beginning of each stack frame.
    StackElemSep string // Separator between elements of each stack frame.
    ErrorSep     string // Separator between each error in the chain.
}

Format defines an error output format to be used with the default formatter.

func NewDefaultFormat Uses

func NewDefaultFormat(withTrace bool) Format

NewDefaultFormat conveniently returns a basic format for the default string formatter.

type Stack Uses

type Stack []StackFrame

Stack is an array of stack frames stored in a human readable format.

type StackFrame Uses

type StackFrame struct {
    Name string
    File string
    Line int
}

StackFrame stores a frame's runtime information in a human readable format.

type UnpackedError Uses

type UnpackedError struct {
    ErrRoot     ErrRoot
    ErrChain    []ErrLink
    ExternalErr string
}

UnpackedError represents complete information about an error.

This type can be used for custom error logging and parsing. Use `eris.Unpack` to build an UnpackedError from any error type. The ErrChain and ErrRoot fields correspond to `wrapError` and `rootError` types, respectively. If any other error type is unpacked, it will appear in the ExternalErr field.

func Unpack Uses

func Unpack(err error) UnpackedError

Unpack returns UnpackedError type for a given golang error type.

Package eris imports 5 packages (graph) and is imported by 10 packages. Updated 2020-01-23. Refresh now. Tools for package owners.