mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
79 lines
2.8 KiB
Go
79 lines
2.8 KiB
Go
// Package ctxerr provides functions to wrap errors with annotations and
|
|
// stack traces, and to handle those errors such that unique instances of
|
|
// those errors will be stored for an amount of time so that it can be
|
|
// used to troubleshoot issues.
|
|
//
|
|
// Typical uses of this package should be to call New or Wrap[f] as close as
|
|
// possible from where the error is encountered (or where it needs to be
|
|
// created for New), and then to call Handle with the error only once, after it
|
|
// bubbled back to the top of the call stack (e.g. in the HTTP handler, or in
|
|
// the CLI command, etc.). It is fine to wrap the error with more annotations
|
|
// along the way, by calling Wrap[f].
|
|
package ctxerr
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/errorstore"
|
|
"github.com/rotisserie/eris"
|
|
)
|
|
|
|
type key int
|
|
|
|
const errHandlerKey key = 0
|
|
|
|
// NewContext returns a context derived from ctx that contains the provided
|
|
// error handler.
|
|
func NewContext(ctx context.Context, eh *errorstore.Handler) context.Context {
|
|
return context.WithValue(ctx, errHandlerKey, eh)
|
|
}
|
|
|
|
func fromContext(ctx context.Context) *errorstore.Handler {
|
|
v, _ := ctx.Value(errHandlerKey).(*errorstore.Handler)
|
|
return v
|
|
}
|
|
|
|
// New creates a new error with the provided error message.
|
|
func New(ctx context.Context, errMsg string) error {
|
|
return ensureCommonMetadata(ctx, errors.New(errMsg))
|
|
}
|
|
|
|
// Wrap annotates err with the provided message.
|
|
func Wrap(ctx context.Context, err error, msg string) error {
|
|
err = ensureCommonMetadata(ctx, err)
|
|
// do not wrap with eris.Wrap, as we want only the root error closest to the
|
|
// actual error condition to capture the stack trace, others just wrap to
|
|
// annotate the error.
|
|
return fmt.Errorf("%s: %w", msg, err)
|
|
}
|
|
|
|
// Wrapf annotates err with the provided formatted message.
|
|
func Wrapf(ctx context.Context, err error, fmsg string, args ...interface{}) error {
|
|
err = ensureCommonMetadata(ctx, err)
|
|
// do not wrap with eris.Wrap, as we want only the root error closest to the
|
|
// actual error condition to capture the stack trace, others just wrap to
|
|
// annotate the error.
|
|
return fmt.Errorf("%s: %w", fmt.Sprintf(fmsg, args...), err)
|
|
}
|
|
|
|
// Handle handles err by passing it to the registered error handler,
|
|
// deduplicating it and storing it for a configured duration.
|
|
func Handle(ctx context.Context, err error) error {
|
|
if eh := fromContext(ctx); eh != nil {
|
|
return eh.Store(err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func ensureCommonMetadata(ctx context.Context, err error) error {
|
|
var sf interface{ StackFrames() []uintptr }
|
|
if err != nil && !errors.As(err, &sf) {
|
|
// no eris error nowhere in the chain, add the common metadata with the stack trace
|
|
// TODO: more metadata from ctx: user, host, etc.
|
|
err = eris.Wrapf(err, "timestamp: %s", time.Now().Format(time.RFC3339))
|
|
}
|
|
return err
|
|
}
|