Documentation
¶
Overview ¶
Package errors provides an API for creating exceptions and alerting.
This is intended to be a replacement for Go's standard library errors package. You can import "github.com/memsql/errors" instead of "errors".
Verbose Messages ¶
Use New or Errorf to produce an error that can be formatted with verbose details, including a stack trace. To see the verbose details, format using `%+v` instead of `%v`, `%s` or calling err.Error().
Alert and Capture ¶
Applications can call RegisterCapture to persist errors to logs, a backend database, or a third-party service. When Alert or Alertf is called, the error will be passed to each registered handler, which is responsible for peristing. The capture handler returns an identifier which becomes part of the error message, as a suffix enclosed in square brackets.
Expand and Expunge ¶
The Expand helper adds information to an error, while Expunge is intended to remove information that end users don't need to see. Both are intended to be deferred, using a pattern like this,
func FindThing(id string) (err error) { // include the id, and verbose stack, with all errors defer errors.Expand(&err, "thing (%q) not found", id) // ... }
When returning errors which wrap other errors, Expunge can be used to hide underlying details, to make a more user-friendly message. It assumes that error message text follows specific conventions, described below.
Message Conventions ¶
Error messages include static parts and dynamic parts. If the error is “/tmp/foo.txt not found”, then “/tmp/foo.txt” is the dynamic part and “not found” is the static part. The dynamic parts present detail to the reader. The static part is useful as well. For example, to determine the line of code where it originated, or how frequently the error is triggered. This package follows a convention to easily work with both static and dynamic parts of error messages:
* Dynamic text SHOULD appear in parenthesis.
* Static text SHOULD be grammatically correct, with or without the dynamic parts.
Following these guidelines, the backend might produce an error: “file ("/tmp/foo.txt”) not found”.
The static text should make sense even when the dynamic parts are removed. Imagine running a whole log file through a regular expression that removes the parentheticals. This stripped log file should still make sense to the reader. In our example, the stripped text would be “file not found”, which makes sense (while just “not found” would not make sense).
The colon character “:” has special significance in error message. It implies that an error message has been “wrapped” with another message, providing additional context.
// avoid this! return errors.Errorf("invalid widget id: %s", id) // do this return errors.Errorf("failed to parse widget id (%q): %w", id, err)
* Error messages SHOULD use the colon “:” only when wrapping another error.
Index ¶
- Variables
- func Alert(err error) error
- func Alertf(format string, a ...interface{}) error
- func Annotate(err error, values ...any) error
- func Annotation[T any](err error) (T, bool)
- func Expand(exception *error, format string, a ...interface{})
- func Expunge(exception *error, format string, a ...interface{})
- func ExpungeOnce(exception *error, format string, a ...interface{})
- func FromPanic(in interface{}) (exception error)
- func New(text string) error
- func RegisterCapture(name CaptureProvider, handler CaptureFunc)
- func UnregisterCapture(name CaptureProvider)
- func Walk(exception error, f func(error) bool)
- func WithStack(err error) error
- func Wrap(exception error, message string) error
- func Wrapf(exception error, format string, a ...interface{}) error
- type CaptureFunc
- type CaptureID
- type CaptureProvider
- type Captured
- type Error
- type Public
- type StackTrace
- type StackTracer
- type String
- type Throttle
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var CaptureTimeout = 500 * time.Millisecond
CaptureTimeout limits how long to wait for a capture ID to be returned from a capture handler.
Functions ¶
func Alert ¶
Alert sends an error to all registered capture handlers. Capture handlers produce verbose logs and alerts. This should be called only for errors that require human attention to address (our developers or SREs). It should not be called for run-of-the-mill errors that are handled in code or returned to portal users.
func Alertf ¶
Alertf produces an error and alerts. It is equivalent to calling Errorf() and then Alert().
func Annotation ¶ added in v0.2.0
Annotation looks at the arguments supplied to Errrof(), Wrapf(), and Annotate(), looking to see if any of them match the type T. If so, it returns the value and true. Otherwise it returns an empty T and false.
func Expand ¶
Expand rewites an error message, when an error is non-nil.
This is intended to be invoked as a deferred function, as a convenient way to add details to an error immediately before returning it.
Example ¶
package main import ( "fmt" "github.com/memsql/errors" ) func main() { // mustBeEven is at odds with odds. mustBeEven := func(input int) error { if (input % 2) == 1 { return errors.New("not even") } return nil } // processEvenNumber uses errors.Expand() to add details when returning non-nil error. processEvenNumber := func(input int) (err error) { // // Expand() adds details to non-nil err, leaves nil error as-is. // defer errors.Expand(&err, "failed to process number (%d)", input) return mustBeEven(input) } err := processEvenNumber(1) // fails because not even fmt.Println(err) err2 := processEvenNumber(42) // succeeds fmt.Println(err2) }
Output: failed to process number (1): not even <nil>
func Expunge ¶
Expunge rewrites an error message, when an error is non-nil. It removes potentially sensitive details from the exception and makes it less verbose, removing text of wrapped errors. It relies on text conventions, see Redact().
Details are redacted from the exception passed in. The format and arguments passed in become part of the new error message. Nothing is redacted from those additional details.
This is intended to be invoked as a deferred function, as a convenient way to remove details from an error immediately before returning it from a public API.
Example ¶
package main import ( "fmt" "github.com/memsql/errors" ) func main() { // mustBeEven is at odds with odds. mustBeEven := func(input int) error { if (input % 2) == 1 { return errors.New("not even") } return nil } // processEvenNumber uses errors.Expand() to add details when returning non-nil error. processEvenNumber := func(input int) (err error) { defer errors.Expand(&err, "failed to process number (%d)", input) return mustBeEven(input) } // ProcessSecretNumber uses errors.Expunge to control details presented when returning non-nil error from a // public-facing API. processSecretNumber := func(input int) (err error) { // // Expunge() redacts details from non-nil err, leaves nil error as-is. // defer errors.Expunge(&err, "unexpected error") return processEvenNumber(input) } err := processSecretNumber(1) // fails because not even fmt.Println(err) err2 := processSecretNumber(42) // succeeds fmt.Println(err2) }
Output: unexpected error: failed to process number <nil>
func ExpungeOnce ¶
ExpungeOnce behaves like Expunge(), except that it leaves an exception as-is if the it has already been expunged.
This is useful when a function has multiple stages, during which different details should be included in the exception. Deferred functions are called in a specific order, and only one deferred ExpungeOnce() will affect the error. In other words, the last reached deferred ExpungeOnce() will determine the final error message.
Example ¶
package main import ( "fmt" "github.com/memsql/errors" ) func main() { // mustBeEven is at odds with odds. mustBeEven := func(input int) error { if (input % 2) == 1 { return errors.New("not even") } return nil } // processEvenNumber uses errors.Expand() to add details when returning non-nil error. processEvenNumber := func(input int) (err error) { defer errors.Expand(&err, "failed to process number (%d)", input) return mustBeEven(input) } // ProcessSecretNumber uses errors.Expunge to control details presented when returning non-nil error from a // public-facing API. processSecretNumber := func(input int) (err error) { // // ExpungeOnce() redacts details from non-nil err, leaves nil error as-is. When multiple ExpungeOnce() are // deferred, only one will affect the error. // defer errors.ExpungeOnce(&err, "secret number must be even") if err = processEvenNumber(input); err != nil { return err } defer errors.ExpungeOnce(&err, "secret number must be divisible by 4") return processEvenNumber(input / 2) } err := processSecretNumber(1) // fails because not even fmt.Println(err) err2 := processSecretNumber(42) // fails because not divisible by 4 fmt.Println(err2) }
Output: secret number must be even: failed to process number secret number must be divisible by 4: failed to process number
func FromPanic ¶
func FromPanic(in interface{}) (exception error)
FromPanic produces an error when passed non-nil input. It accepts input of any type, in order to support being invoked with what is returned from recover().
defer func() { if err = errors.FromPanic(recover()); err != nil { ... } }()
func New ¶
New emulates the behavior of stdlib's errors.New(), and includes a stack trace with the error.
func RegisterCapture ¶
func RegisterCapture(name CaptureProvider, handler CaptureFunc)
RegisterCapture adds a handler to the set that will be invoked each time an error is captured.
func UnregisterCapture ¶
func UnregisterCapture(name CaptureProvider)
func Walk ¶
Walk visits each error in a tree of errors wrapping other errors.
The handler func, f, takes in the error being visited. The walk continues if the handler returns true, and does not continue if the handler returns false.
func WithStack ¶
WithStack produces an error that includes a stack trace. Note that if the wrapped error already has a stack, that error is returned without modification. Thus only the first call to WithStack will produce a stack trace. In other words when an error is wrapped multiple times, it is the stack of the earliest wrapped error which has priority.
To add information to an error message, use Errorf() instead. This function is provided to add a stack trace to a third-party error without otherwise altering the error text.
func Wrap ¶
Wrap returns nil when the exception passed in is nil; otherwise, it returns an error with message text that wraps exception.
This function provides an alternative to
err = f() if err != nil { return errors.Errorf("failed objective: %w", err) } return nil
can be written as
return errors.Wrap(f(), "failed objective")
Types ¶
type CaptureFunc ¶
CaptureFunc is a handler invoked to capture an error.
Any mechanism that can save an error may provide a capture handler. For example sentry, a log, etc... The CaptureID returned should be a way to find the error among other errors captured by the mechanism.
type CaptureID ¶
type CaptureID string // may be a URL or any string that allows a captured error to be looked up
func LogCapture ¶
LogCapture is a simple capture handler that writes exception to log.
type CaptureProvider ¶
type CaptureProvider string // i.e. "sentry"
type Captured ¶
type Captured struct {
// contains filtered or unexported fields
}
Captured marks and wraps an error that has been "captured", meaning it has been logged verbosely or stored in a way that can be looked up later.
func (*Captured) Format ¶
Format produces a message with capture ID appended. The intention is to allow the captured details to be looked up, by engineers with access to the capture mechanism.
func (*Captured) ID ¶
func (e *Captured) ID(provider CaptureProvider) CaptureID
ID returns an identifier created when a capture handler recorded the error.
type Error ¶
type Error struct {
// contains filtered or unexported fields
}
Error implements Go's error interface; and can format verbose messages, including stack traces.
func Errorf ¶
Errorf produces an error with a formatted message including dynamic arguments.
Callers are encouraged to include all relevant arguments in a well-constructed error message. Most arguments are stored to provide additional metadata if the error is later captured. Arguments will not be stored when apparently redundant, for example a wrapped error or string included in error text.
type Public ¶
type Public struct {
// contains filtered or unexported fields
}
func Redact ¶
Redact removes potential sensitive details from an error, making the message safe to display to an unprivileged user.
Redact removes content in parenthesis. That is, it expects only errors that follow the convention that potentially sensitive information appears in parentheses. Also that errors are relatively simple, i.e. without nested parentheses.
type StackTrace ¶
type StackTrace = pkgerrors.StackTrace
type StackTracer ¶
type StackTracer interface {
StackTrace() pkgerrors.StackTrace
}
StackTracer is exported so that external packages can detect whether a err has stack trace associated.
type String ¶
type String string
String provides a simple mechanism to define const errors. This enable packages to export simple errors using
const ErrNoDroids = errors.String("these are not the droids you're looking for")
type Throttle ¶
A Throttle will alert, until threshold is reached. After threshold is reached, errors are no longer alerted. See Throttle.Alert() for additional details. A throttle allows you to capture the first error(s) encountered on a given line of code, but not subsequent errors.
Use a throttle when you believe that an error warrants investigation if it ever occurs, but it is not necessary to capture every time it occurs.
The throttle is not persisted across restarts, so errors will be captured for each replica of a service and each time a replica is restarted. So, if you specify a Threshold of one, you might see two captures if the service has two replicas, or four after those replicas have restarted, etc.
func (*Throttle) Alert ¶
Alert will capture an exception identically to errors.Alert, until some threshold number of errors has been alerted. After that threshold amount, subsequent errors are returned without capture.
The goal is to log and capture errors, if they occur; while not capturing so many that noise exceeds signal in logs and sentry.