terrors

package module
v0.0.0-...-073654f Latest Latest
Warning

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

Go to latest
Published: Mar 12, 2024 License: MIT Imports: 4 Imported by: 57

README

terrors

Build Status GoDoc

Terrors is a package for wrapping Golang errors. Terrors provides additional context to an error, such as an error code and a stack trace.

Terrors is built and used at Monzo.

Usage

Terrors can be used to wrap any object that satisfies the error interface:

terr := terrors.Wrap(err, map[string]string{"context": "my_context"})

Terrors can be instantiated directly:

err := terrors.New("not_found", "object not found", map[string]string{
	"context": "my_context"
})

Terrors offers built-in functions for instantiating Errors with common codes:

err := terrors.NotFound("config_file", "config file not found", map[string]string{
	"context": my_context
})

Terrors provides functions for matching specific Errors:

err := NotFound("handler_missing", "Handler not found", nil)
fmt.Println(Matches(err, "not_found.handler_missing")) // true
Retryability

Terrors contains the ability to declare whether or not an error is retryable. This property is derived from the error code if not specified explicitly.

When using the the wrapping functionality (e.g. Wrap, Augment, Propagate), the retryability of an error is preserved as expected. Importantly, it is also preserved when constructing a new error from a causal error with NewInternalWithCause.

API

Full API documentation can be found on godoc

Install

$ go get -u github.com/monzo/terrors

License

Terrors is licenced under the MIT License

Documentation

Overview

Package terrors implements an error wrapping library.

Terrors are used to provide context to an error, offering a stack trace and user defined error parameters.

Terrors can be used to wrap any object that satisfies the error interface:

terr := terrors.Wrap(err, map[string]string{"context": "my_context"})

Terrors can be instantiated directly:

err := terrors.New("not_found", "object not found", map[string]string{
	"context": "my_context"
})

Terrors offers built-in functions for instantiating Errors with common codes:

err := terrors.NotFound("config_file", "config file not found", map[string]string{
	"context": my_context
})

Index

Examples

Constants

View Source
const (
	ErrBadRequest         = "bad_request"
	ErrBadResponse        = "bad_response"
	ErrForbidden          = "forbidden"
	ErrInternalService    = "internal_service"
	ErrNotFound           = "not_found"
	ErrPreconditionFailed = "precondition_failed"
	ErrTimeout            = "timeout"
	ErrUnauthorized       = "unauthorized"
	ErrUnknown            = "unknown"
	ErrRateLimited        = "rate_limited"
)

Generic error codes. Each of these has their own constructor for convenience. You can use any string as a code, just use the `New` method. Warning: any new generic error code must be added to GenericErrorCodes.

Variables

GenericErrorCodes is a list of all well known generic error codes.

Functions

func Augment

func Augment(err error, context string, params map[string]string) error

Augment adds context to an existing error. If the error given is not already a terror, a new terror is created.

func Is

func Is(err error, code ...string) bool

Is checks whether an error is a given code. Similarly to `errors.Is`, this unwinds the error stack and checks each underlying error for the code. If any match, this returns true. Note that Is only behaves differently to PrefixMatches when errors in the stack have different codes. For example, this is the case when errors are initialized with NewInternalWithCause, but not with Augment. We prefer this over using a method receiver on the terrors Error, as the function signature requires an error to test against, and checking against terrors would requite creating a new terror with the specific code.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable returns true if the error is a terror and whether the error was caused by an action which can be retried.

func Marshal

func Marshal(e *Error) *pe.Error

Marshal an error into a protobuf for transmission

func Matches

func Matches(err error, match string) bool

Matches returns true if the error is a terror error and the string returned from error.Error() contains the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param` or even on the more descriptive message Deprecated: Please use `Is` instead. Note that `Is` will attempt to match each error in the stack using `PrefixMatches`, so if you were previously matching against a part of the string returned from error.Error() that is _not_ the prefix, then this will be a breaking change. In this case you should update the string to match the prefix. If this is not possible, you can match against the entire error string explicitly, for example:

strings.Contains(err.Error(), "context deadline exceeded")

But we consider this bad practice and is part of the motivation for deprecating Matches in the first place.

Example
err := NotFound("handler_missing", "Handler not found", nil)
fmt.Println(Matches(err, "not_found.handler_missing"))
Output:

true

func PrefixMatches

func PrefixMatches(err error, prefixParts ...string) bool

PrefixMatches returns true if the error is a terror and the string returned from error.Error() starts with the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param`. Each dotted part can be passed as a separate argument e.g. `terrors.PrefixMatches(terr, terrors.ErrBadRequest, "missing_param")` is the same as terrors.PrefixMatches(terr, "bad_request.missing_param")` Deprecated: Please use `Is` instead.

func Propagate

func Propagate(err error) error

Propagate an error without changing it. This is equivalent to `return err` if the error is already a terror. If it is not a terror, this function will create one, and set the given error as the cause. This is a drop-in replacement for `terrors.Wrap(err, nil)` which adds causal chain functionality.

func StackStringWithMaxSize

func StackStringWithMaxSize(p *Error, sizeLimit int) string

func Wrap

func Wrap(err error, params map[string]string) error

Wrap takes any error interface and wraps it into an Error. This is useful because an Error contains lots of useful goodies, like the stacktrace of the error. NOTE: If `err` is already an `Error`, it will add the params passed in to the params of the Error Deprecated: Use Augment instead.

func WrapWithCode

func WrapWithCode(err error, params map[string]string, code string) error

WrapWithCode wraps an error with a custom error code. If `err` is already an `Error`, it will add the params passed in to the params of the error Deprecated: Use Augment instead. If you need to set the code of the error, then you should return a new error instead. For example

terrors.WrapWithCode(err, map[string]string{"foo": "bar"}, "bad_request.failed")

would become

terrors.BadRequest("failed", err.Error(), map[string]string{"foo": "bar"})
Example
fn := "not/a/file"
_, err := os.Open(fn)
if err != nil {
	errParams := map[string]string{
		"filename": fn,
	}
	err = WrapWithCode(err, errParams, ErrNotFound)
	terr := err.(*Error)
	fmt.Println(terr.Error())
	
Output:

not_found: open not/a/file: no such file or directory

Types

type Error

type Error struct {
	Code        string            `json:"code"`
	Message     string            `json:"message"`
	Params      map[string]string `json:"params"`
	StackFrames stack.Stack       `json:"stack"`

	// Exported for serialization, but you should use Retryable to read the value.
	IsRetryable *bool `json:"is_retryable"`

	// Exported for serialization, but you should use Unexpected to read the value.
	IsUnexpected *bool `json:"is_unexpected"`

	// Incremented each time the error is marshalled so that we can tell (approximately) how many services the error
	// has propagated through.  Higher level code can use this to influence decisions, for example it may only be
	// desirable to retry on an error that's only been marshalled once to avoid retries on top of retries... ad nauseam
	MarshalCount int `json:"marshal_count"`

	// When errors are marshalled certain information is lost (e.g. the 'cause').  This means if an error travels through
	// a number of services (and it's potentially augmented at each hop) that the core error message may be lost. The
	// history of an error is often a helpful debugging aid, so MessageChain is used to track this.
	MessageChain []string `json:"message_chain"`
	// contains filtered or unexported fields
}

Error is terror's error. It implements Go's error interface.

func BadRequest

func BadRequest(code, message string, params map[string]string) *Error

BadRequest creates a new error to represent an error caused by the client sending an invalid request. This is non-retryable unless the request is modified.

func BadResponse

func BadResponse(code, message string, params map[string]string) *Error

BadResponse creates a new error representing a failure to response with a valid response Examples of this would be a handler returning an invalid message format

func Forbidden

func Forbidden(code, message string, params map[string]string) *Error

Forbidden creates a new error representing a resource that cannot be accessed with the current authorisation credentials. The user may need authorising, or if authorised, may not be permitted to perform this action

func InternalService

func InternalService(code, message string, params map[string]string) *Error

InternalService creates a new error to represent an internal service error. Only use internal service error if we know very little about the error. Most internal service errors will come from `Wrap`ing a vanilla `error` interface. Errors returned by this function are considered to be retryable by default. Consider using NonRetryableInternalService if retries are not desirable.

func New

func New(code string, message string, params map[string]string) *Error

New creates a new error for you. Use this if you want to pass along a custom error code. Otherwise use the handy shorthand factories below

func NewInternalWithCause

func NewInternalWithCause(err error, message string, params map[string]string, subCode string) *Error

NewInternalWithCause creates a new Terror from an existing error. The new error will always have the code `ErrInternalService`. The original error is attached as the `cause`, and can be tested with the `Is` function. You probably want to use the `Augment` func instead; only use this if you need to set a subcode on an error.

func NonRetryableInternalService

func NonRetryableInternalService(code, message string, params map[string]string) *Error

NonRetryableInternalService creates a new error to represent an internal service error. Only use internal service error if we know very little about the error. Most internal service errors will come from `Wrap`ing a vanilla `error` interface. Errors returned by this function are not considered to be retryable by default.

func NotFound

func NotFound(code, message string, params map[string]string) *Error

NotFound creates a new error representing a resource that cannot be found. In some cases this is not an error, and would be better represented by a zero length slice of elements

func PreconditionFailed

func PreconditionFailed(code, message string, params map[string]string) *Error

PreconditionFailed creates a new error indicating that one or more conditions given in the request evaluated to false when tested on the server.

func RateLimited

func RateLimited(code, message string, params map[string]string) *Error

RateLimited creates a new error indicating that the request has been rate-limited, and that the caller should back-off.

func Timeout

func Timeout(code, message string, params map[string]string) *Error

Timeout creates a new error representing a timeout from client to server

func Unauthorized

func Unauthorized(code, message string, params map[string]string) *Error

Unauthorized creates a new error indicating that authentication is required, but has either failed or not been provided.

func Unmarshal

func Unmarshal(p *pe.Error) *Error

Unmarshal a protobuf error into a local error

func (*Error) Error

func (p *Error) Error() string

Error returns a string message of the error. It will contain the code and error message. If there is a causal chain, the message from each error in the chain will be added to the output.

func (*Error) ErrorMessage

func (p *Error) ErrorMessage() string

ErrorMessage returns a string message of the error. It will contain the error message, but not the code. If there is a causal chain, the message from each error in the chain will be added to the output.

func (*Error) LogMetadata

func (p *Error) LogMetadata() map[string]string

LogMetadata implements the logMetadataProvider interface in the slog library which means that the error params will automatically be merged with the slog metadata. Additionally we put stack data in here for slog use.

func (*Error) Matches

func (p *Error) Matches(match string) bool

Matches returns whether the string returned from error.Error() contains the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param` or even on the more descriptive message Deprecated: Please use `Is` instead. See docs for `Matches` for breaking change risks.

func (*Error) PrefixMatches

func (p *Error) PrefixMatches(prefixParts ...string) bool

PrefixMatches returns whether the string returned from error.Error() starts with the given param string. This means you can match the error on different levels e.g. dotted codes `bad_request` or `bad_request.missing_param`. Each dotted part can be passed as a separate argument e.g. `terr.PrefixMatches(terrors.ErrBadRequest, "missing_param")` is the same as `terr.PrefixMatches("bad_request.missing_param")` Deprecated: Please use `Is` instead.

func (*Error) Retryable

func (p *Error) Retryable() bool

Retryable determines whether the error was caused by an action which can be retried.

func (*Error) SetIsRetryable

func (p *Error) SetIsRetryable(value bool)

func (*Error) SetIsUnexpected

func (p *Error) SetIsUnexpected(value bool)

SetIsUnexpected can be used to explicitly mark an error as unexpected or not. In practice the vast majority of code should not need to use this. An example use case might be when returning a validation error that must mean there is a coding mistake somewhere (e.g. default statement in a switch that is never expected to be taken). By marking the error as unexpected there is a greater chance that an alert will be sent.

func (*Error) StackString

func (p *Error) StackString() string

StackString formats the stacks from the terror chain as a string. If we encounter more than one terror in the chain with a stack frame, we'll print each one, separated by three hyphens on their own line.

func (*Error) StackTrace

func (p *Error) StackTrace() []uintptr

StackTrace returns a slice of program counters taken from the stack frames. This adapts the terrors package to allow stacks to be reported to Sentry correctly.

func (*Error) Unexpected

func (p *Error) Unexpected() bool

Unexpected states whether an error is not expected to occur. In many cases this will be due to a bug, e.g. due to a defensive check failing

func (*Error) Unwrap

func (p *Error) Unwrap() error

Unwrap retruns the cause of the error. It may be nil.

func (*Error) VerboseString

func (p *Error) VerboseString() string

VerboseString returns the error message, stack trace and params

Directories

Path Synopsis
totally stolen from https://github.com/stvp/rollbar/blob/master/stack.go
totally stolen from https://github.com/stvp/rollbar/blob/master/stack.go

Jump to

Keyboard shortcuts

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