errors

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Apr 20, 2022 License: Apache-2.0 Imports: 11 Imported by: 40

README

Errors with a stack trace

pkg.go.dev Go Report Card pipeline status coverage report

A Go package providing errors with a stack trace.

Features:

  • Based of github.com/pkg/errors with similar API, addressing many its open issues. In many cases it can be used as a drop-in replacement. At the same time compatible with github.com/pkg/errors errors.
  • Uses standard error wrapping (available since Go 1.13).
  • Provides errors.Errorf which supports %w format verb to both wrap and record a stack trace at the same time (if not already recorded).
  • Provides errors.E type to be used instead of standard error to annotate which functions return errors with a stack trace.
  • Clearly defines what are differences and expected use cases for:
  • Provides errors.Base function to create errors without a stack trace to be used as base errors for errors.Is and errors.As.
  • Differentiates between wrapping and recording a cause: only errors.Wrap records a cause, while other functions are error transformers, wrapping the original.
  • Novice friendly formatting of a stack trace when error is formatted using %+v: tells what is the order of the stack trace and what is the relation between wrapped errors.
  • Makes sure a stack trace is not recorded multiple times unnecessarily.
  • Provide optional details map on all errors returned by this package.
  • Errors implement MarshalJSON and can be marshaled into JSON.

Installation

This is a Go package. You can add it to your project using go get:

go get gitlab.com/tozd/go/errors

There is also a read-only GitHub mirror available, if you need to fork the project there.

Usage

See full package documentation with examples on pkg.go.dev.

Why a new Go errors package?

github.com/pkg/errors package is archived and not developed anymore, with many issues not addressed (primarily because many require some backward incompatible change). At the same time it has been made before Go 1.13 added official support for wrapping errors and it does not (and cannot, in backwards compatible way) fully embrace it. This package takes what is best from github.com/pkg/errors, but breaks things a bit to address many of the open issues community has identified since then and to modernize it to today's Go:

  • Message formatting WithMessage vs. Wrap: #114
  • Do not re-add stack trace if one is already there: #122
  • Be explicit when you want to record a stack trace again vs. do not if it already exists: #75 #158 #242
  • StackTrace() should return []uintptr: #79
  • Do not assume Cause cannot return nil: #89
  • Obtaining only message from Wrap: #93
  • WithMessage always prefixes the message: #102
  • Differentiate between "wrapping" and "causing": #112
  • Support for base errors: #130 #160
  • Support for a different delimiter by supporting Errorf: #207 #226
  • Support for Errorf wrapping an error: #244
  • Having each function wrap only once: #223

What are main differences from github.com/pkg/errors?

  • The stackTracer interface's StackTrace() method returns []uintptr and not custom type StackTrace.
  • All error-wrapping functions return errors which implement the standard unwrapper interface, but only errors.Wrap records a cause error and returns an error which implements the causer interface.
  • All error-wrapping functions wrap the error into only one new error.
  • Errorf supports %w.
  • Errors formatted using %+v include lines Stack trace (most recent call first): and The above error was caused by the following error: to make it clearer how is the stack trace formatted and how are multiple errors related to each other.
  • Only errors.Wrap always records the stack trace while other functions do not record if it is already present.
  • errors.Cause repeatedly unwraps the error until it finds one which implements the causer interface, and then return its cause.

It looks like Wrap should be named Cause. Why it is not?

For legacy reasons because this package builds on shoulders of github.com/pkg/errors. Every modification to errors made through this package is done through wrapping so that original error is always available. Wrap wraps the error to records the cause.

  • cockroachdb/errors – Go errors with every possible feature you might ever need in your large project. This package aims to stay lean and be more or less just a drop-in replacement for core Go errors, but with stack traces (and few utility functions for common cases).

Documentation

Overview

Package errors provides errors with a recorded stack trace.

The traditional error handling idiom in Go is roughly akin to

if err != nil {
        return err
}

which when applied recursively up the call stack results in error reports without a stack trace or context. The errors package provides error handling primitives to annotate errors along the failure path in a way that does not destroy the original error.

Adding a stack trace to an error

When interacting with code which returns errors without a stack trace, you can upgrade that error to one with a stack trace using errors.WithStack. For example:

func readAll(r io.Reader) ([]byte, errors.E) {
        data, err := ioutil.ReadAll(r)
        if err != nil {
                return nil, errors.WithStack(err)
        }
        return data, nil
}

errors.WithStack records the stack trace at the point where it was called, so use it as close to where the error originated as you can get so that the recorded stack trace is more precise.

The example above uses errors.E for the returned error type instead of the standard error type. This is not required, but it tells Go that you expect that the function returns only errors with a stack trace and Go type system then helps you find any cases where this is not so.

errors.WithStack does not record the stack trace if it is already present in the error so it is safe to call it if you are unsure if the error contains a stack trace.

Errors with a stack trace implement the following interface, returning program counters of function invocations:

type stackTracer interface {
        StackTrace() []uintptr
}

You can use standard runtime.CallersFrames to obtain stack trace frame information (e.g., function name, source code file and line).

Although the stackTracer interface is not exported by this package, it is considered a part of its stable public interface.

Adding context to an error

Sometimes an error occurs in a low-level function and the error messages returned from it are too low-level, too. You can use errors.Wrap to construct a new higher-level error while recording the original error as a cause.

image, err := readAll(imageFile)
if err != nil {
        return nil, errors.Wrap(err, "reading image failed")
}

In the example above we returned a new error with a new message, hidding the low-level details. The returned error implements the following interface

type causer interface {
        Cause() error
}

which enables access to the underlying low-level error. You can also use errors.Cause to obtain the cause.

Although the causer interface is not exported by this package, it is considered a part of its stable public interface.

Sometimes you do not want to hide the error message but just add to it. You can use errors.WithMessage, which adds a prefix to the existing message, or errors.Errorf, which gives you more control over the new message.

errors.WithMessage(err, "reading image failed")
errors.Errorf("reading image failed (%w)", err)

Example new messages could then be, respectively:

"reading image failed: connection error"
"reading image failed (connection error)"

Adding details to an error

Errors returned by this package implement the detailer interface

type detailer interface {
        Details() map[string]interface{}
}

which enables access to a map with optional additional information about the error. Returned map can be modified in-place to store additional information. You can also use errors.Details and errors.AllDetails to access details.

Working with the hierarchy of errors

Errors which implement the following standard unwrapper interface

type unwrapper interface {
        Unwrap() error
}

form a hierarchy of errors where a wrapping error points its parent, wrapped, error. Errors returned from this package implement this interface to return the original error, when there is one. This enables us to have constant base errors which we annotate with a stack trace before we return them:

var AuthenticationError = errors.Base("authentication error")
var MissingPassphraseError = errors.BaseWrap(AuthenticationError, "missing passphrase")
var InvalidPassphraseError = errors.BaseWrap(AuthenticationError, "invalid passphrase")

func authenticate(passphrase string) errors.E {
        if passphrase == "" {
                return errors.WithStack(MissingPassphraseError)
        } else if passphrase != "open sesame" {
                return errors.WithStack(InvalidPassphraseError)
        }
        return nil
}

We can use errors.Is to determine which error has been returned:

if errors.Is(err, MissingPassphraseError) {
        fmt.Println("Please provide a passphrase to unlock the doors.")
}

Works across the hierarchy, too:

if errors.Is(err, AuthenticationError) {
        fmt.Println("Failed to unlock the doors.")
}

Formatting errors

All errors with a stack trace returned from this package implement fmt.Formatter interface and can be formatted by the fmt package. The following verbs are supported:

%s    the error message
%v    same as %s
%+v   together with the error message include also the stack trace,
      ends with a newline
Example (StackTrace)
package main

import (
	"fmt"
	"runtime"

	"gitlab.com/tozd/go/errors"
)

func getErr() error {
	return errors.New("foobar")
}

func main() {
	type stackTracer interface {
		StackTrace() []uintptr
	}

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

	frames := runtime.CallersFrames(err.StackTrace())
	frame, _ := frames.Next()
	fmt.Printf("%s\n\t%s:%d", frame.Function, frame.File, frame.Line)

	// Example output:
	// gitlab.com/tozd/go/errors_test.getErr
	//	/home/user/errors/example_test.go:213
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AllDetails added in v0.4.0

func AllDetails(err error) map[string]interface{}

AllDetails returns a map build from calling The Details method on err and populating the map with key/value pairs which are not yet present. Afterwards, the err is unwrapped and the process is repeated.

func As

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

As finds the first error in err's chain that matches target, and if so, sets target to that error value and returns true. Otherwise, it returns false.

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.

An error type might provide an As method so it can be treated as if it were a different error type.

As panics if target is not a non-nil pointer to either a type that implements error, or to any interface type.

This function is a proxy for standard errors.As.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

type MyError struct {
	code    int
	message string
}

func (e MyError) Error() string {
	return e.message
}

func (e MyError) Code() int {
	return e.code
}

var (
	BadRequestError = &MyError{400, "error"}
	NotFoundError   = &MyError{404, "not found"}
)

func getMyErr() error {
	return NotFoundError
}

func main() {
	err := getMyErr()

	var myErr *MyError
	if errors.As(err, &myErr) {
		fmt.Printf("code: %d", myErr.Code())
	}
}
Output:

code: 404

func Base

func Base(message string) error

Base returns an error with the supplied message. Each call to Base returns a distinct error value even if the message is identical. It does not record a stack trace.

Use this for a constant base error you convert to an actual error you return with WithStack. This base error you can then use in Is and As calls.

This function is a proxy for standard errors.New.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

whoops

func BaseWrap

func BaseWrap(err error, message string) error

BaseWrap returns an error with the supplied message, wrapping an existing error err. Each call to BaseWrap returns a distinct error value even if the message is identical. It does not record a stack trace.

Use this when you want to create a hierarchy of base errors and you want to fully control the error message.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	base := errors.Base("error")
	valueError := errors.BaseWrap(base, "value")
	fmt.Println(valueError)
}
Output:

value

func BaseWrapf

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

BaseWrapf returns an error with the supplied message formatted according to a format specifier. Each call to BaseWrapf returns a distinct error value even if the message is identical. It does not record a stack trace. It does not support %w format verb. Use Basef if you need it.

Use this when you want to create a hierarchy of base errors and you want to fully control the error message.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	base := errors.Base("error")
	valueError := errors.BaseWrapf(base, "value %d", 2)
	fmt.Println(valueError)
}
Output:

value 2

func Basef

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

Basef returns an error with the supplied message formatted according to a format specifier. Each call to Basef returns a distinct error value even if the message is identical. It does not record a stack trace. It supports %w format verb to wrap an existing error.

Use this for a constant base error you convert to an actual error you return with WithStack. This base error you can then use in Is and As calls. Use %w format verb when you want to create a hierarchy of base errors.

This function is a proxy for standard fmt.Errorf.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	err := errors.Basef("whoops #%d", 2)
	fmt.Println(err)
}
Output:

whoops #2

func Cause

func Cause(err error) error

Cause returns the result of calling the Cause method on err, if err's type contains an Cause method returning error. Otherwise, the err is unwrapped and the process is repeated. If unwrapping is not possible, Cause returns nil.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	base := errors.Base("error")
	wrapped := errors.Wrap(base, "wrapped")
	fmt.Println(errors.Cause(wrapped))
}
Output:

error

func Details added in v0.4.0

func Details(err error) map[string]interface{}

Details returns the result of calling the Details method on err, or nil if it is not available.

func Is

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.

An error type might provide an Is method so it can be treated as equivalent to an existing error. For example, if MyError defines

func (m MyError) Is(target error) bool { return target == fs.ErrExist }

then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for an example in the standard library.

This function is a proxy for standard errors.Is.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	base := errors.Base("error")
	valueError := errors.BaseWrap(base, "value")
	fmt.Println(errors.Is(valueError, base))
}
Output:

true

func Unwrap

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.

This function is a proxy for standard errors.Unwrap.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	base := errors.Base("error")
	withPrefix := errors.WithMessage(base, "prefix")
	fmt.Println(withPrefix)
	fmt.Println(errors.Unwrap(withPrefix))
}
Output:

prefix: error
error

Types

type E

type E interface {
	error
	// contains filtered or unexported methods
}

E interface can be used in as a return type instead of the standard error interface to annotate which functions return an error with a stack trace. This is useful so that you know when you should use WithStack (for functions which do not return E) and when not (for functions which do return E). If you call WithStack on an error with a stack trace nothing bad happens (same error is simply returned), it just pollutes the code. So this interface is defined to help.

func Errorf

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

Errorf return an error with the supplied message formatted according to a format specifier. It supports %w format verb to wrap an existing error. Errorf also records the stack trace at the point it was called, unless wrapped error already have a stack trace.

When formatting the returned error using %+v, formatting is not delegated to the wrapped error (when there is one), giving you full control of the message and formatted error.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	err := errors.Errorf("whoops: %s", "foo")
	fmt.Printf("%+v", err)

	// Example output:
	// whoops: foo
	// Stack trace (most recent call first):
	// gitlab.com/tozd/go/errors_test.ExampleErrorf
	// 	/home/user/errors/example_test.go:134
	// testing.runExample
	// 	/usr/local/go/src/testing/run_example.go:64
	// testing.runExamples
	// 	/usr/local/go/src/testing/example.go:44
	// testing.(*M).Run
	// 	/usr/local/go/src/testing/testing.go:1505
	// main.main
	// 	_testmain.go:95
	// runtime.main
	// 	/usr/local/go/src/runtime/proc.go:255
	// runtime.goexit
	// 	/usr/local/go/src/runtime/asm_amd64.s:1581
}
Output:

Example (Wrap)
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	base := errors.Base("whoops")
	err := errors.Errorf("oh noes (%w)", base)
	fmt.Printf("%+v", err)

	// Example output:
	// oh noes (whoops)
	// Stack trace (most recent call first):
	// gitlab.com/tozd/go/errors_test.ExampleErrorf_wrap
	// 	/home/user/errors/example_test.go:189
	// testing.runExample
	// 	/usr/local/go/src/testing/run_example.go:64
	// testing.runExamples
	// 	/usr/local/go/src/testing/example.go:44
	// testing.(*M).Run
	// 	/usr/local/go/src/testing/testing.go:1505
	// main.main
	// 	_testmain.go:99
	// runtime.main
	// 	/usr/local/go/src/runtime/proc.go:255
	// runtime.goexit
	// 	/usr/local/go/src/runtime/asm_amd64.s:1581
}
Output:

func New

func New(message string) E

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"

	"gitlab.com/tozd/go/errors"
)

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

whoops
Example (Printf)
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	err := errors.New("whoops")
	fmt.Printf("%+v", err)

	// Example output:
	// whoops
	// Stack trace (most recent call first):
	// gitlab.com/tozd/go/errors_test.ExampleNew_printf
	// 	/home/user/errors/example_test.go:16
	// testing.runExample
	// 	/usr/local/go/src/testing/run_example.go:64
	// testing.runExamples
	// 	/usr/local/go/src/testing/example.go:44
	// testing.(*M).Run
	// 	/usr/local/go/src/testing/testing.go:1505
	// main.main
	// 	_testmain.go:87
	// runtime.main
	// 	/usr/local/go/src/runtime/proc.go:255
	// runtime.goexit
	// 	/usr/local/go/src/runtime/asm_amd64.s:1581
}
Output:

func WithDetails added in v0.4.0

func WithDetails(err error) E

WithDetails wraps err implementing the detailer interface to access a map with optional additional information about the error.

If err does not have a stack trace, then this call is equivalent to calling WithStack, annotating err with a stack trace as well.

Use this when you have an err which implements stackTracer interface but does not implement detailer interface as well.

It is useful when err does implement detailer interface, but you want to reuse same err multiple times (e.g., pass same err to multiple goroutines), adding different details each time. Calling WithDetails wraps err and adds an additional and independent layer of details on top of any existing details.

func WithMessage

func WithMessage(err error, message string) E

WithMessage annotates err with a prefix message. If err does not have a stack trace, stack strace is recorded as well.

It does not support controlling the delimiter. Use Errorf if you need that.

When formatting the returned error using %+v, formatting is delegated to the wrapped error, prefixing it with the message. The stack trace is added only if the wrapped error does not already have it.

If err is nil, WithMessage returns nil.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

oh noes: whoops
Example (Printf)
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

	// Example output:
	// oh noes: whoops
	// Stack trace (most recent call first):
	// gitlab.com/tozd/go/errors_test.ExampleWithMessage_printf
	// 	/home/user/errors/example_test.go:46
	// testing.runExample
	// 	/usr/local/go/src/testing/run_example.go:64
	// testing.runExamples
	// 	/usr/local/go/src/testing/example.go:44
	// testing.(*M).Run
	// 	/usr/local/go/src/testing/testing.go:1505
	// main.main
	// 	_testmain.go:97
	// runtime.main
	// 	/usr/local/go/src/runtime/proc.go:255
	// runtime.goexit
	// 	/usr/local/go/src/runtime/asm_amd64.s:1581
}
Output:

func WithMessagef

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

WithMessagef annotates err with a prefix message formatted according to a format specifier. If err does not have a stack trace, stack strace is recorded as well.

It does not support %w format verb or controlling the delimiter. Use Errorf if you need that.

When formatting the returned error using %+v, formatting is delegated to the wrapped error, prefixing it with the message. The stack trace is added only if the wrapped error does not already have it.

If err is nil, WithMessagef returns nil.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

oh noes #2: whoops

func WithStack

func WithStack(err error) E

WithStack annotates err with a stack trace at the point WithStack was called, if err does not already have a stack trace. If err is nil, WithStack returns nil.

When formatting the returned error using %+v, formatting is delegated to the wrapped error. The stack trace is added only if the wrapped error does not already have it.

Use this instead of Wrap when you just want to convert an existing error into one with a stack trace. Use it as close to where the error originated as you can get.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

whoops
Example (Printf)
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

func main() {
	base := errors.Base("whoops")
	err := errors.WithStack(base)
	fmt.Printf("%+v", err)

	// Example output:
	// whoops
	// Stack trace (most recent call first):
	// gitlab.com/tozd/go/errors_test.ExampleWithStack_printf
	// 	/home/user/errors/example_test.go:54
	// testing.runExample
	// 	/usr/local/go/src/testing/run_example.go:64
	// testing.runExamples
	// 	/usr/local/go/src/testing/example.go:44
	// testing.(*M).Run
	// 	/usr/local/go/src/testing/testing.go:1505
	// main.main
	// 	_testmain.go:91
	// runtime.main
	// 	/usr/local/go/src/runtime/proc.go:255
	// runtime.goexit
	// 	/usr/local/go/src/runtime/asm_amd64.s:1581
}
Output:

func Wrap

func Wrap(err error, message string) E

Wrap returns an error annotating err with a stack trace at the point Wrap is called, and the supplied message. Wrapping is done even if err already has a stack trace. It records the original error as a cause. If err is nil, Wrap returns nil.

When formatting the returned error using %+v, formatting of the cause is delegated to the wrapped error.

Use this when you want to make a new error, preserving the cause of the new error.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

oh noes
Example (Printf)
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

	// Example output:
	// oh noes
	// Stack trace (most recent call first):
	// gitlab.com/tozd/go/errors_test.ExampleWrap_printf
	// 	/home/user/errors/example_test.go:86
	// testing.runExample
	// 	/usr/local/go/src/testing/run_example.go:64
	// testing.runExamples
	// 	/usr/local/go/src/testing/example.go:44
	// testing.(*M).Run
	// 	/usr/local/go/src/testing/testing.go:1505
	// main.main
	// 	_testmain.go:93
	// runtime.main
	// 	/usr/local/go/src/runtime/proc.go:255
	// runtime.goexit
	// 	/usr/local/go/src/runtime/asm_amd64.s:1581
	//
	// The above error was caused by the following error:
	//
	// whoops
	// Stack trace (most recent call first):
	// gitlab.com/tozd/go/errors_test.ExampleWrap_printf
	// 	/home/user/errors/example_test.go:85
	// testing.runExample
	// 	/usr/local/go/src/testing/run_example.go:64
	// testing.runExamples
	// 	/usr/local/go/src/testing/example.go:44
	// testing.(*M).Run
	// 	/usr/local/go/src/testing/testing.go:1505
	// main.main
	// 	_testmain.go:93
	// runtime.main
	// 	/usr/local/go/src/runtime/proc.go:255
	// runtime.goexit
	// 	/usr/local/go/src/runtime/asm_amd64.s:1581
}
Output:

func Wrapf

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

Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the supplied message formatted according to a format specifier. Wrapping is done even if err already has a stack trace. It records the original error as a cause. It does not support %w format verb. If err is nil, Wrapf returns nil.

When formatting the returned error using %+v, formatting of the cause is delegated to the wrapped error.

Use this when you want to make a new error, preserving the cause of the new error.

Example
package main

import (
	"fmt"

	"gitlab.com/tozd/go/errors"
)

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

oh noes #2

Jump to

Keyboard shortcuts

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