derr

package module
v0.0.0-...-4dc9d80 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2020 License: Apache-2.0 Imports: 23 Imported by: 0

README

dfuse Errors Library

reference License

This repository contains all common code for handling errors across our various services. It is part of dfuse.

Usage

  1. Use derr.Wrap(err, "some message") to wrap any calls to a sub-system that could have yielded some derr.ErrorResponse (so it is passed to the user untouched).
  2. Use derr.SomeError (see errors.go) wherever possible to have consistent errors throughout our system. If you think the error you are creating can be shared, put it in errors.go and use that.
  3. Craft your own custom Error builder (in your project's errors.go, see eosws) and use that in your code. Craft a meaningful derr.C (or derr.ErrorCode) with a good name (see below), reuse where possible.
gRPC APIs

We create derr.Status errors when we want to set a specific error code. We call derr.Wrap to prefix the error message going out (without altering the outgoing Status Code).

Returning plain fmt.Errorf() upstream implies returning a codes.Internal gRPC status code.

Missing a derr.Wrap() kills the links and removes the StatusCode, so use Wrap (or Wrapf) whenever you want to add a prefix.

REST APIs

All error should be created by defining a properly named function that receives a context.Context object as well as specialized parameters to craft a proper error message and details. This specific error function should be re-using under the cover one the underlying pre-defined HTTP error creator, see list.

The most important thing is that all error should have a proper unique code defined using derr.C("value") type alias which is a sugar syntax for derr.ErrorCode("string_code_error").

This is of the uttermost importance so that we can easily grep all our source code files to extract the used specific error codes across all our services for documentation purposes.

The string code defined must be unique among all our services, human readable, should clearly represent the error in few words, should be in snake_case format and should end with _error.

For example, let's say that in your micro service, you would like to define a specialized bad request error when a particular request is invalid due to block number being too low.

Here the piece of code that your should do:

func BlockNumTooLowError(ctx context.Context, blockNum uint32, thresholdBlockNum uint32) *derr.ErrorResponse {
	return HTTPBadRequestError(ctx, err, derr.C("block_num_too_low_error"), "The requested block num is too low",
        "actual_block_num", blockNum,
        "threshold_block_num", thresholdBlockNum,
    )
}

Each of generic HTTP error creator receives the context.Context object. This context is required to extract the traceID from the context so that the trace ID is returned back to the user for future analysis of the problem.

Moreover, the idiomatic way to group errors is to put them all in a file errors.go in the root package of the service so they are all easily discoverable in a single location.

JSON Format

Here the explained JSON format:

{
  "code": "specifc_error_code",
  "trace_id": "%s",
  "message": "Sepcific error code message, audience is the end user."
  "details": {
    "key": <value>,
    ...
  },
}
Parameter Explanation
code The unique error representing this error, should be a human readable summary of the error, in snake case.
trace_id The unique trace id to further debug that error, the trace id can also be correlated in the logs.
message A message describing the error. The audience of the message is the end user. Should be a full sentence ending with a dot.
details A key-value map of extra details specific to the error. Usually contains faulty parameters and extra details about the error.
Wrap

This package is aware of wrapped errors through the github.com/pkg/errors package.

As a convenience, the package provides shortcuts to the Wrap and Wrapf of the pkgErrors package so you don't need to import it directly using a custom name like pkgErrors (to avoid conflict with the standard errors package).

You can simply use derr.Wrap and derr.Wrapf to wrap your errors with newer contextual errors.

Write Error

The package provides a facility to write any error object back to the user. The derr.WriteError(ctx context.Context, w http.ResponseWriter, message string, err error) will correctly find the derr.ErrorResponse out of the err parameter received and will output to the user.

If no derr.ErrorResponse can be found, automatically, the error is wrapped in an derr.UnexpectedError and returned like that to the user. This behavior will result in a generic error message appearing for the user.

Note that WriteError is also logging the error at the same occasion, removing the burden to log the error yourself. If the error written back to the user generates a >= 500 error code, the Error level is used. Otherwise, a Debug level is used to log the error.

This error logging will ultimately trickle down to our monitoring infrastructure, so if you use WriteError, be sure to not log it again!

Contributing

Issues and PR in this repo related strictly to the derr library.

Report any protocol-specific issues in their respective repositories

Please first refer to the general dfuse contribution guide, if you wish to contribute to this code base.

License

Apache 2.0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	HTTPBadRequestError                   = newErrorClass(http.StatusBadRequest)
	HTTPUnauthorizedError                 = newErrorClass(http.StatusUnauthorized)
	HTTPPaymentRequiredError              = newErrorClass(http.StatusPaymentRequired)
	HTTPForbiddenError                    = newErrorClass(http.StatusForbidden)
	HTTPNotFoundError                     = newErrorClass(http.StatusNotFound)
	HTTPMethodNotAllowedError             = newErrorClass(http.StatusMethodNotAllowed)
	HTTPNotAcceptableError                = newErrorClass(http.StatusNotAcceptable)
	HTTPProxyAuthRequiredError            = newErrorClass(http.StatusProxyAuthRequired)
	HTTPRequestTimeoutError               = newErrorClass(http.StatusRequestTimeout)
	HTTPConflictError                     = newErrorClass(http.StatusConflict)
	HTTPGoneError                         = newErrorClass(http.StatusGone)
	HTTPLengthRequiredError               = newErrorClass(http.StatusLengthRequired)
	HTTPPreconditionFailedError           = newErrorClass(http.StatusPreconditionFailed)
	HTTPRequestEntityTooLargeError        = newErrorClass(http.StatusRequestEntityTooLarge)
	HTTPRequestURITooLongError            = newErrorClass(http.StatusRequestURITooLong)
	HTTPUnsupportedMediaTypeError         = newErrorClass(http.StatusUnsupportedMediaType)
	HTTPRequestedRangeNotSatisfiableError = newErrorClass(http.StatusRequestedRangeNotSatisfiable)
	HTTPExpectationFailedError            = newErrorClass(http.StatusExpectationFailed)
	HTTPTeapotError                       = newErrorClass(http.StatusTeapot)
	HTTPUnprocessableEntityError          = newErrorClass(http.StatusUnprocessableEntity)
	HTTPLockedError                       = newErrorClass(http.StatusLocked)
	HTTPFailedDependencyError             = newErrorClass(http.StatusFailedDependency)
	HTTPUpgradeRequiredError              = newErrorClass(http.StatusUpgradeRequired)
	HTTPPreconditionRequiredError         = newErrorClass(http.StatusPreconditionRequired)
	HTTPTooManyRequestsError              = newErrorClass(http.StatusTooManyRequests)
	HTTPRequestHeaderFieldsTooLargeError  = newErrorClass(http.StatusRequestHeaderFieldsTooLarge)
	HTTPUnavailableForLegalReasonsError   = newErrorClass(http.StatusUnavailableForLegalReasons)
)
View Source
var (
	HTTPInternalServerError                = newErrorClass(http.StatusInternalServerError)
	HTTPNotImplementedError                = newErrorClass(http.StatusNotImplemented)
	HTTPBadGatewayError                    = newErrorClass(http.StatusBadGateway)
	HTTPServiceUnavailableError            = newErrorClass(http.StatusServiceUnavailable)
	HTTPGatewayTimeoutError                = newErrorClass(http.StatusGatewayTimeout)
	HTTPHTTPVersionNotSupportedError       = newErrorClass(http.StatusHTTPVersionNotSupported)
	HTTPVariantAlsoNegotiatesError         = newErrorClass(http.StatusVariantAlsoNegotiates)
	HTTPInsufficientStorageError           = newErrorClass(http.StatusInsufficientStorage)
	HTTPLoopDetectedError                  = newErrorClass(http.StatusLoopDetected)
	HTTPNotExtendedError                   = newErrorClass(http.StatusNotExtended)
	HTTPNetworkAuthenticationRequiredError = newErrorClass(http.StatusNetworkAuthenticationRequired)
)
View Source
var ErrorCheck = Check

Deprecated: use `Check` with `derr.Check`).

Functions

func Check

func Check(prefix string, err error)

func Find

func Find(err error, matcher func(err error) bool) error

Find walks the error(s) stack (causes chain) and return the first error matching the `matcher` function received in argument.

Act exactly like `errors.Is` but using a matcher function instead of trying to match a particular address.

func FindFirstMatching deprecated

func FindFirstMatching(err error, matcher func(err error) bool) error

FindFirstMatching walks the error(s) stack (causes chain) and return the first error matching the `matcher` function received in argument.

Deprecated: FindFirstMatching has been renamed to `Find`.

func HasAny deprecated

func HasAny(err error, cause error) bool

HasAny returns `true` if the `err` argument or any of its cause(s) is equal to `cause` argument, `false` otherwise.

Deprecated: HasAny has been renamed to `Is`, use it instead of this method.

func Is

func Is(err error, cause 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 (or Cause).

func IsClientSideNetworkError

func IsClientSideNetworkError(err error) bool

IsClientSideNetworkError returns wheter the error received is a network error caused by the client side that could not be possibily handled correctly on the server side anyway.

func IsShuttingDown

func IsShuttingDown() bool

func Retry

func Retry(retries uint64, f func(ctx context.Context) error) error

func RetryContext

func RetryContext(ctx context.Context, retries uint64, f func(ctx context.Context) error) error

func SetupSignalHandler

func SetupSignalHandler(gracefulShutdownDelay time.Duration) <-chan os.Signal

this is a graceful delay to allow residual traffic sent by the load balancer to be processed without returning 500. Once the delay has passed then the service can be shutdown

func Status

func Status(code codes.Code, message string) error

func Statusf

func Statusf(code codes.Code, format string, args ...interface{}) error

func Walk

func Walk(err error, processor func(err error) (bool, error)) error

Walk traverse error causes in a top to bottom fashion. Starting from the top `err` received, will invoke `processor(err)` with it. If the `processor` returns `true`, check if `err` has a cause and continue walking it like this recursively. If `processor` return a `non-nil` value, stop walking at this point. If `processor` returns an `error` stop walking from there and bubble up the error through `Walk` return value.

Returns an `error` if `processor` returned an `error`, `nil` otherwise

func Wrap

func Wrap(err error, message string) error

Wrap is a shortcut for `pkgErrors.Wrap` (where `pkgErrors` is `github.com/pkg/errors`)

func WrapCode

func WrapCode(code codes.Code, err error, message string) error

func Wrapf

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

Wrapf is a shortcut for `pkgErrors.Wrapf` (where `pkgErrors` is `github.com/pkg/errors`)

func WrapfCode

func WrapfCode(code codes.Code, err error, format string, args ...interface{}) error

func WriteError

func WriteError(ctx context.Context, w http.ResponseWriter, message string, err error)

WriteError writes the receiver error to HTTP and log it into a Zap logger at the same time with the right level based on the actual status code. The `WriteError` handles various type for the `err` parameter.

Types

type ErrorCode

type ErrorCode string

func C

func C(code string) ErrorCode

C is a sugar syntax for `derr.ErrorCode("a_string_code")` (sugared to `derr.C("a_string_code")`)

type ErrorResponse

type ErrorResponse struct {
	Code    ErrorCode              `json:"code"`
	TraceID string                 `json:"trace_id"`
	Status  int                    `json:"-"`
	Message string                 `json:"message"`
	Details map[string]interface{} `json:"details,omitempty"`
	Causer  error                  `json:"-"`
}

func HTTPErrorFromStatus

func HTTPErrorFromStatus(status int, ctx context.Context, cause error, code ErrorCode, message interface{}, keyvals ...interface{}) *ErrorResponse

HTTPErrorFromStatus can be used to programmaticaly route the right status to one of the HTTP error class above

func InvalidJSONError

func InvalidJSONError(ctx context.Context, err error) *ErrorResponse

func MissingBodyError

func MissingBodyError(ctx context.Context) *ErrorResponse

func RequestValidationError

func RequestValidationError(ctx context.Context, errors url.Values) *ErrorResponse

func ServiceUnavailableError

func ServiceUnavailableError(ctx context.Context, cause error, serviceName string) *ErrorResponse

ServiceUnavailableError represents a failure at the transport layer to reach a given micro-service. Note that while `serviceName` is required, it's not directly available to final response for now, will probably encrypt it into an opaque string if you ever make usage of it

func ToErrorResponse

func ToErrorResponse(ctx context.Context, err error) *ErrorResponse

ToErrorResponse turns a plain `error` interface into a proper `ErrorResponse` object. It does so with the following rules:

- If `err` is already an `ErrorResponse`, turns it into such and returns it. - If `err` was wrapped, find the most cause which is an `ErrorResponse` and returns it. - If `err` is a status.Status (or one that was wrapped), convert it to an ErrorResponse - Otherwise, return an `UnexpectedError` with the cause sets to `err` received.

func UnexpectedError

func UnexpectedError(ctx context.Context, cause error) *ErrorResponse

func (*ErrorResponse) Cause

func (e *ErrorResponse) Cause() error

func (*ErrorResponse) Error

func (e *ErrorResponse) Error() string

func (*ErrorResponse) ResponseStatus

func (e *ErrorResponse) ResponseStatus() int

Jump to

Keyboard shortcuts

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