errors

package
v0.0.0-...-58a9b13 Latest Latest
Warning

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

Go to latest
Published: Apr 2, 2026 License: BSD-3-Clause Imports: 16 Imported by: 14

README

Package errors

When dealing with unexpected or undesired behavior on any system (like issues and exceptions) the more information available, structured and otherwise, the better. Preserving error structure and context is particularly important since, in general, string comparisons on error messages are vulnerable to injection and can even cause security problems. On distributed systems is very useful, and often required, to preserve these details across service boundaries as well.

The main goals of this package are:

  • Provide a simple, extensible and "familiar" implementation that can be easily used as a drop-in replacement for the standard "errors" package and popular 3rd party libraries.
  • Preserve the entire structure of errors across the wire using pluggable codecs.
  • Produce portable and PII-safe error reports. These reports can then be sent to any 3rd party service or webhook.
  • Enable fast, reliable and secure determination of whether a particular cause is present (not relying on the presence of a substring in the error message).
  • Being easily composable; by making it extensible with additional error annotations and supporting special behavior on custom error types.

Inspiration

This library is mainly inspired on the original https://github.com/cockroachdb/errors package, while adding some specific adjustments. For additional information about the original package refer to PR-36987.

Motivation

Go provides 4 "idiomatic" ways to inspect errors:

  1. Reference comparison to global objects. err == io.EOF

  2. Type assertions to known error types. err.(*os.PathError)

  3. Predicate provided by library. os.IsNotExists(err)

  4. String comparison on the result of err.Error()

Method 1 breaks down when using wrapped errors, or when transferring errors over the network.

Method 2 breaks down if the error object is converted to a different type. When wire representations are available, the method is generally reliable; however, if errors are implemented as a chain of causes, care should be taken to perform the test on all the intermediate levels.

Method 3 is generally reliable although the predicates in the standard library obviously do not know about any additional custom types. Also, the implementation of the predicate method can be cumbersome if one must test errors from multiple packages (dependency cycles). This method loses its reliability if the predicate itself relies on one of the other methods in a way that's unreliable.

Method 4 is the most problematic and unreliable.

Usage

An error leaf is an object that implements the error interface, but does not refer to another error via Unwrap() and/or Cause().

  • To create a new error instance use constructor methods New() or Errorf(). The stack trace of the error will point to the line the method is called.
  • You can use Opaque to capture an error cause but make it invisible to Unwrap() or Is(). This is particularly useful when a new error occurs while handling another one, and the original error must be "hidden".

An error wrapper is an object that implements the error interface, and also refers to another error via Unwrap() and/or Cause().

  • Wrapper constructors, i.e., Wrap() can be applied safely to a nil error; the function will behave as no-op in this case.

Custom error types

You can personalize the behavior of your custom error types by providing implementations for the following methods:

  • Cause() error
  • Unwrap() error
  • Is(target error) bool

Redactable Details

You can easily generate a redactable message container that supports manually hiding and disclosing any additional parameters. This is particularly useful to avoid accidentally dumping sensitive details to logs or error messages.

// Create a redactable message. All arguments are considered sensitive.
secret := SensitiveMessage("my name is %s (or %s)", "bond", "007")

// Printing the message will remove any arguments used to generate the
// message.
fmt.Println(secret)

// You can use the `%+v` formatting verb to manually disclose the provided
// arguments.
fmt.Printf("%+v", secret)

// When using a redactable message to create an error instance, secret details
// will never be printed out; not even when using the `%+v` formatting verb to
// generate a portable stacktrace.
err := New(secret)
fmt.Printf("%+v", err)

Example

Consider the following dummy code consisting of several levels of function calling; each one wrapping potential errors bubbling up from lower levels.

// Nested chain of function calls.
// sampleA -> wraps
//  sampleB -> wraps
//   sampleC -> wraps
//    sampleD -> wraps
//     sampleE = returns the original error
func sampleA() error { return Wrap(sampleB(), "a") }
func sampleB() error { return Wrap(sampleC(), "b") }
func sampleC() error { return Wrap(sampleD(), "c") }
func sampleD() error { return Wrap(sampleE(), "d") }
func sampleE() error { return New("deep error") }

Your function call this call, receives and error and you need to inspect it and use it productively.

func myAwesomeFunction() {
  err := sampleA()

  // The most basic thing you can do with the error is log/print it.
  fmt.Println(err.Error())
    // This will print:
    // a: b: c: d: deep error

  // You can also use `Unwrap` to go one level down in the error
  // "chain". For example:
  fmt.Println(Unwrap(err).Error())
    // This will print:
    // b: c: d: deep error

  // Or go straight to the root cause, i.e., the deep-most error
  // in the chain.
  fmt.Println(Cause(err).Error())
    // This will print:
    // deep error
}
Stack Traces

Of course there are more interesting details you can get from your errors. When diagnosing issues, the more details at your disposal the better. An important tool at your disposal are stack traces.

func myAwesomeFunction() {
  err := sampleA()

  // Using the '%v' format command with your error value will
  // output a trace formatted as in the standard library
  // `runtime/debug.Stack()`
  fmt.Printf("%v", err)

The trace produced will be something similar to:

a: b: c: d: deep error
/home/ben/go/src/bryk-io/pkg/errors/api_test.go:199 (0x1024a8e4b)
  sampleE: func sampleE() error { return New("deep error") }
/home/ben/go/src/bryk-io/pkg/errors/api_test.go:198 (0x1024a8e38)
  sampleD: func sampleD() error { return Wrap(sampleE(), "d") }
/home/ben/go/src/bryk-io/pkg/errors/api_test.go:197 (0x1024a8deb)
  sampleC: func sampleC() error { return Wrap(sampleD(), "c") }
/home/ben/go/src/bryk-io/pkg/errors/api_test.go:196 (0x1024a8d9b)
  sampleB: func sampleB() error { return Wrap(sampleC(), "b") }
/home/ben/go/src/bryk-io/pkg/errors/api_test.go:195 (0x1024a8d4b)
  sampleA: func sampleA() error { return Wrap(sampleB(), "a") }
/home/ben/go/src/bryk-io/pkg/errors/api_test.go:14 (0x1024a6cdb)
  TestSample: err := sampleA()
/opt/homebrew/Cellar/go/1.19.5/libexec/src/testing/testing.go:1446 (0x1023ebe1b)
  tRunner: fn(t)

The standard trace, while helpful, is filled with local details. For example all the paths from my local filesystem. For this reason, this package also supports the %+v format command that will produce a more portable and friendlier stack trace output.

func myAwesomeFunction() {
  err := sampleA()

  // Using the '%v' format command with your error value will
  // output a more portable trace.
  fmt.Printf("%+v", err)

This time, the trace produced will be formatted like the following:

a: b: c: d: deep error
‹0› GOPATH/src/bryk-io/pkg/errors/api_test.go:199 (0x102190e4b)
  sampleE: func sampleE() error { return New("deep error") }
‹1› GOPATH/src/bryk-io/pkg/errors/api_test.go:198 (0x102190e38)
  sampleD: func sampleD() error { return Wrap(sampleE(), "d") }
‹2› GOPATH/src/bryk-io/pkg/errors/api_test.go:197 (0x102190deb)
  sampleC: func sampleC() error { return Wrap(sampleD(), "c") }
‹3› GOPATH/src/bryk-io/pkg/errors/api_test.go:196 (0x102190d9b)
  sampleB: func sampleB() error { return Wrap(sampleC(), "b") }
‹4› GOPATH/src/bryk-io/pkg/errors/api_test.go:195 (0x102190d4b)
  sampleA: func sampleA() error { return Wrap(sampleB(), "a") }
‹5› GOPATH/src/bryk-io/pkg/errors/api_test.go:14 (0x10218ecdb)
  TestSample: err := sampleA()
‹6› GOROOT/src/testing/testing.go:1446 (0x1020d3e1b)
  tRunner: fn(t)
Additional Information

Stack traces are a great place to start diagnosing issues, but of course, the more information available to you the better. This package allow you to annotate your error with additional contextual information such as: hints, events and tags.

func myAwesomeFunction() {
  err := sampleA()

  // First, cast the error as an "Error" instance provided
  // by this package.
  var te *Error
  if As(err, &te) {
    // Hints provide free-form contextual details.
    te.AddHint("hints can provide additional context about an issue")
    te.AddHint("this was just a test")

    // Tags are usually used for: grouping, filtering and statistic
    // analysis in general.
    te.SetTag("env", "testing")
    te.SetTag("user", "rick")
    te.SetTag("paying_customer", true)

    // You can also add relevant events produced along
    // the error chain. This is often useful when diagnosing
    // complex issues involving many components/services.
    te.AddEvent(Event{
      Kind:    "console",
      Message: "additional debugging information",
    })
  }

  // Then, use the "extended" format command `%+v`. This time
  // the output will include not only the portable stack trace
  // but also all the additional contextual information available
  // in the error.
  fmt.Printf("%+v", te)

The output produced from this error will contain a lot more details that, hopefully, will help diagnose and fix the issue.

a: b: c: d: deep error
‹0› GOPATH/src/bryk-io/pkg/errors/api_test.go:202 (0x1008a909b)
 sampleE: func sampleE() error { return New("deep error") }
‹1› GOPATH/src/bryk-io/pkg/errors/api_test.go:201 (0x1008a9088)
 sampleD: func sampleD() error { return Wrap(sampleE(), "d") }
‹2› GOPATH/src/bryk-io/pkg/errors/api_test.go:200 (0x1008a903b)
 sampleC: func sampleC() error { return Wrap(sampleD(), "c") }
‹3› GOPATH/src/bryk-io/pkg/errors/api_test.go:199 (0x1008a8feb)
 sampleB: func sampleB() error { return Wrap(sampleC(), "b") }
‹4› GOPATH/src/bryk-io/pkg/errors/api_test.go:198 (0x1008a8f9b)
 sampleA: func sampleA() error { return Wrap(sampleB(), "a") }
‹5› GOPATH/src/bryk-io/pkg/errors/api_test.go:14 (0x1008a6deb)
 TestSample: err := sampleA()
‹6› GOROOT/src/testing/testing.go:1446 (0x1007ebe1b)
 tRunner: fn(t)
‹hints›
 - hints can provide additional context about an issue
 - this was just a test
‹tags›
 - env=testing
 - user=rick
 - paying_customer=true
‹events›
 - (console) additional debugging information
Reports

Finally, having all that information displayed in the console is already helpful, but being able to collect it elsewhere will be even more so. That's what Report allows us to do. Report takes in and error instance and a Codec and is responsible of producing a portable representation of the error's contents. As a reference, this package includes a CodecJSON implementation.

For example, you can produce a JSON report of the above error and submitted via HTTP to your monitoring system.

func myAwesomeFunction() {
  err := sampleA()

  // First, cast the error as an "Error" instance provided
  // by this package.
  var te *Error
  if As(err, &te) {
    // ... add same additional details as before ...
    // omitted for brevity
  }

  // Produce JSON error report
  js, _ := Report(err, CodecJSON(true))
  fmt.Printf("%s", js)

The produced report will be:

{
  "error": "a: b: c: d: deep error",
  "events": [
    {
      "kind": "console",
      "message": "additional debugging information",
      "stamp": 1674845182688
    }
  ],
  "hints": [
    "hints can provide additional context about an issue",
    "this was just a test"
  ],
  "stamp": 1674845182688,
  "tags": {
    "env": "testing",
    "paying_customer": true,
    "user": "rick"
  },
  "trace": [
    {
      "file": "GOPATH/src/bryk-io/pkg/errors/api_test.go",
      "line_number": 198,
      "function": "sampleE",
      "package": "go.bryk.io/pkg/errors",
      "source_line": "func sampleE() error { return New(\"deep error\") }",
      "program_counter": 4297461947
    },
    {
      "file": "GOPATH/src/bryk-io/pkg/errors/api_test.go",
      "line_number": 197,
      "function": "sampleD",
      "package": "go.bryk.io/pkg/errors",
      "source_line": "func sampleD() error { return Wrap(sampleE(), \"d\") }",
      "program_counter": 4297461928
    },
    {
      "file": "GOPATH/src/bryk-io/pkg/errors/api_test.go",
      "line_number": 196,
      "function": "sampleC",
      "package": "go.bryk.io/pkg/errors",
      "source_line": "func sampleC() error { return Wrap(sampleD(), \"c\") }",
      "program_counter": 4297461851
    },
    {
      "file": "GOPATH/src/bryk-io/pkg/errors/api_test.go",
      "line_number": 195,
      "function": "sampleB",
      "package": "go.bryk.io/pkg/errors",
      "source_line": "func sampleB() error { return Wrap(sampleC(), \"b\") }",
      "program_counter": 4297461771
    },
    {
      "file": "GOPATH/src/bryk-io/pkg/errors/api_test.go",
      "line_number": 194,
      "function": "sampleA",
      "package": "go.bryk.io/pkg/errors",
      "source_line": "func sampleA() error { return Wrap(sampleB(), \"a\") }",
      "program_counter": 4297461691
    },
    {
      "file": "GOPATH/src/bryk-io/pkg/errors/api_test.go",
      "line_number": 14,
      "function": "TestSample",
      "package": "go.bryk.io/pkg/errors",
      "source_line": "err := sampleA()",
      "program_counter": 4297453035
    },
    {
      "file": "GOROOT/src/testing/testing.go",
      "line_number": 1446,
      "function": "tRunner",
      "package": "testing",
      "source_line": "fn(t)",
      "program_counter": 4296687131
    }
  ]
}

Documentation

Overview

Package errors provides an enhanced error management library.

When dealing with unexpected or undesired behavior on any system (like issues and exceptions) the more information available, structured and otherwise, the better. Preserving error structure and context is particularly important since, in general, string comparisons on error messages are vulnerable to injection and can even cause security problems. On distributed systems is very useful, and often required, to preserve these details across service boundaries as well.

The main goals of this package are:

  • Provide a simple, extensible and "familiar" implementation that can be easily used as a drop-in replacement for the standard "errors" package and popular 3rd party libraries.
  • Preserve the entire structure of errors across the wire using pluggable codecs.
  • Produce portable and PII-safe error reports. These reports can then be sent to any 3rd party service or webhook.
  • Enable fast, reliable and secure determination of whether a particular cause is present (not relying on the presence of a substring in the error message).
  • Being easily composable; by making it extensible with additional error annotations and supporting special behavior on custom error types.

This library is mainly inspired on the original https://github.com/cockroachdb/errors package, while adding some specific adjustments. For additional information about the original package refer to [PR-36987](https://github.com/cockroachdb/cockroach/pull/36987)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func As

func As(err error, target interface{}) bool

As unwraps `err` sequentially looking for an error that can be assigned to `target`, which must be a pointer. If it succeeds, it performs the assignment and returns true. Otherwise, it returns false. `target` must be a pointer to an interface or to a type implementing the error interface.

func Cause

func Cause(err error) error

Cause will recursively retrieve the topmost error which does not provide a cause, which is assumed to be the original failure condition.

func Combine

func Combine(err, other error) error

Combine the error given as first argument with an annotation that carries the error given as second argument. The second error does not participate in cause analysis (Is, IsAny, ...) and is only revealed when reporting out the error.

Considerations:

  • If `other` is nil, the first error is returned as-is.
  • If `err` doesn't support adding additional details, it is returned as-is.

func Errorf

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

Errorf returns a new root error (i.e., without a cause) instance which stacktrace will point to the line of code that called this function.

If the format specifier includes a `%w` verb with an error operand, the returned error will implement an Unwrap method returning the operand. It is invalid to include more than one `%w` verb or to supply it with an operand that does not implement the `error` interface. The `%w` verb is otherwise a synonym for `%v`.

func Is

func Is(src, target error) bool

Is detects whether the error is equal to a given error. Errors are considered equal by this function if:

  • Are both the same object
  • If `src` provides a custom `Is(e error) bool` implementation it will be used and the result returned
  • If `target` provides a custom `Is(e error) bool` implementation it will be used and the result returned
  • Comparison is true between `target` and `src` cause
  • Comparison is true between `src` and `target` cause

func IsAny

func IsAny(src error, target ...error) bool

IsAny detects whether the error is equal to any of the provided target errors. This method uses the same equality rules as `Is`.

func New

func New(e interface{}) error

New returns a new root error (i.e., without a cause) instance from the given value. If the provided `e` value is:

  • An `Error` instance created with this package it will be returned as-is.
  • An `error` value, will be set as the root cause for the new error instance.
  • Any other value, will be passed to fmt.Errorf("%v") and the resulting error value set as the root cause for the new error instance.

The stacktrace will point to the line of code that called this function.

func Opaque

func Opaque(err error) error

Opaque returns an error with the same formatting as `err` but that does not match `err` and cannot be unwrapped. This will essentially drop existing error context, useful when requiring a processing "barrier". This method returns a new root error (i.e., without a cause) instance.

func Report

func Report(err error, cc Codec) ([]byte, error)

Report an error instance by generating a portable/transmissible representation of it using the provided codec.

func Unwrap

func Unwrap(err error) error

Unwrap unpacks wrapped errors. If its argument's type has an `Unwrap` method, it calls the method once. Otherwise, it returns nil.

func WithStack

func WithStack(err error) error

WithStack returns a new root error (i.e., without a cause) instance which stacktrace will point to the line of code that called this function.

func WithStackAt

func WithStackAt(err error, at int) error

WithStackAt returns a new root error (i.e., without a cause) instance which stacktrace will point to the line of code that called this function; minus number of stacks specified in `at`.

func Wrap

func Wrap(e error, prefix string) error

Wrap a given error into another one, this allows to create or expand an error cause chain. The provided `e` error will be registered as the root cause for the returned error instances. If `e` includes a stacktrace, it will be preserved.

func Wrapf

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

Wrapf returns a wrapped version of the provided error using a formatted string as prefix.

Types

type Codec

type Codec interface {
	// Encodes an error instance and produce a report.
	Marshal(err error) ([]byte, error)

	// Decoded an error report and restore an error instance.
	// If this operation fails for whatever reason `ok` should
	// be `false`.
	Unmarshal(src []byte) (ok bool, err error)
}

Codec implementations provide a pluggable way to manage error across service boundaries; for example when transmitting error messages through a network.

func CodecJSON

func CodecJSON(pretty bool) Codec

CodecJSON encodes error data as JSON documents. If `pretty` is set to `true` the output will be indented for readability.

type Error

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

Error is an error with an attached stacktrace. It can be used wherever the builtin error interface is expected.

func FromRecover

func FromRecover(src interface{}) *Error

FromRecover is a utility function to facilitate obtaining a useful error instance from a panicked goroutine. To use it, simply pass the native `recover()` to it from within the panicking goroutine:

recovered := FromRecover(recover())

func ParsePanic

func ParsePanic(text string) (*Error, error)

ParsePanic allows you to get an error object from the output of a go program that panicked.

func (*Error) AddEvent

func (e *Error) AddEvent(ev Event)

AddEvent registers an additional event on the error instance.

func (*Error) AddHint

func (e *Error) AddHint(hint string)

AddHint registers additional information on the error instance.

func (*Error) Cause

func (e *Error) Cause() error

Cause of the error. Obtained by traversing the entire error stack until an error with a `cause` value of 'nil'. Errors without cause are expected to be the root error of a failure condition.

func (*Error) Error

func (e *Error) Error() string

Error returns the underlying error's message.

func (*Error) Events

func (e *Error) Events() []Event

Events associated to the error, if any. Events usually provide valuable information on when/how an exception occurred.

func (*Error) Format

func (e *Error) Format(s fmt.State, verb rune)

Format error values using the escape codes defined by fmt.Formatter. The following verbs are supported:

%s   error message. Simply prints the basic error message as a
     string representation.
%v   basic format. Print the error including its stackframe formatted
     as in the standard library `runtime/debug.Stack()`.
%+v  extended format. Returns the stackframe formatted as in the
     standard library `runtime/debug.Stack()` but replacing the values
     for `GOPATH` and `GOROOT` on file paths. This makes the traces
     more portable and avoid exposing (noisy) local system details.

func (*Error) Hints

func (e *Error) Hints() []string

Hints provide additional context to an error in the form of meaningful text messages. If no hints are set on the error instance this method returns `nil`.

func (*Error) PortableTrace

func (e *Error) PortableTrace() []StackFrame

PortableTrace returns the frames in the callers stack attempting to remove any paths specific to the local system, making the information a bit more readable and portable.

func (*Error) SetTag

func (e *Error) SetTag(key string, value interface{})

SetTag registers a specific key/value pair on the error instance; replacing any previously set values under the same key.

func (*Error) StackTrace

func (e *Error) StackTrace() []StackFrame

StackTrace returns the frames in the callers stack.

func (*Error) Stamp

func (e *Error) Stamp() int64

Stamp returns error creation UNIX timestamp (in milliseconds).

func (*Error) Tags

func (e *Error) Tags() map[string]interface{}

Tags provide additional context to an error in the form of arbitrary key/value pairs. If no tags are set on the error instance this method returns `nil`.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the next error in the error chain. If there is no next error, Unwrap returns nil.

type Event

type Event struct {
	// Kind can be used to group specific events into categories or groups.
	Kind string `json:"kind,omitempty"`

	// Short and concise description of the event.
	Message string `json:"message,omitempty"`

	// UNIX timestamp (in milliseconds).
	Stamp int64 `json:"stamp,omitempty"`

	// Additional data associated with an event.
	Attributes map[string]interface{} `json:"attributes,omitempty"`
}

Event instances can be used to provided additional contextual information for an error.

type HasStack

type HasStack interface {
	StackTrace() []StackFrame
}

HasStack is implemented by error types that natively provide robust stack traces.

type Redactable

type Redactable interface {
	// Redact the sensitive details out of the message.
	Redact() string

	// Disclose the sensitive details included in the message. Use with care.
	Disclose() string
}

Redactable represents a message that contains some form of private or sensitive information that should be redacted when used as an error or log message.

func SensitiveMessage

func SensitiveMessage(format string, args ...interface{}) Redactable

SensitiveMessage returns a redactable message container. The `args` included can be redacted out of the message; specially useful when used as an error or log message. The returned message container can be specially formatted using the standard escape codes defined by `fmt.Formatter`. The following verbs are supported:

%s   return the redacted version of the message.
%v   return the redacted version of the message.
%+v  return the full version of the message.

type StackFrame

type StackFrame struct {
	// The path to the file containing this ProgramCounter.
	File string `json:"filename,omitempty"`

	// The line number in that file.
	LineNumber int `json:"line_number,omitempty"`

	// The name of the function that contains this ProgramCounter.
	Function string `json:"function,omitempty"`

	// The package that contains this function.
	Package string `json:"package,omitempty"`

	// The line of code (from File and Line) of the original source,
	// if available.
	SourceLine string `json:"source_line,omitempty"`

	// The underlying ProgramCounter.
	ProgramCounter uintptr `json:"program_counter,omitempty"`
}

A StackFrame contains all necessary information about a specific line in a callstack.

func (StackFrame) Format

func (sf StackFrame) Format(s fmt.State, verb rune)

Format error values using the escape codes defined by fmt.Formatter. The following verbs are supported:

%v   see '%s'
%s   basic format. Returns the stackframe formatted as in the
     standard library `runtime/debug.Stack()`.
%+v  extended format. Returns the stackframe formatted as in the
     standard library `runtime/debug.Stack()` but replacing the values
     for `GOPATH` and `GOROOT` on file paths. This makes the traces
     more portable and avoid exposing (noisy) local system details.

Jump to

Keyboard shortcuts

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