mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-26 03:40:33 +01:00
425 lines
12 KiB
Go
425 lines
12 KiB
Go
package errors
|
|
|
|
import (
|
|
"errors" //nolint:depguard
|
|
"fmt"
|
|
"log/slog"
|
|
"maps"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
)
|
|
|
|
// base is the fundamental struct that implements the error interface.
|
|
type base struct {
|
|
// t denotes the custom type of the error.
|
|
t typ
|
|
// c denotes the short code for the error message.
|
|
c Code
|
|
// m contains error message passed through errors.New.
|
|
m string
|
|
// e is the actual error being wrapped.
|
|
e error
|
|
// u denotes the url for the documentation (if present) for the error.
|
|
u string
|
|
// a denotes any additional error messages (if present).
|
|
// NOTE: use attrs['suggestions'] for additional structured suggestions instead of this field.
|
|
a []string
|
|
// s contains the stacktrace captured at error creation time.
|
|
s fmt.Stringer
|
|
// r is the retry strategy for the error, if applicable.
|
|
r retry
|
|
// attrs contains any additional attributes for the error, if present.
|
|
// attrs is declared to be map[string]any, however map[string][]string is expected to be used for values.
|
|
attrs map[string]any
|
|
}
|
|
|
|
// Stacktrace returns the stacktrace captured at error creation time, formatted as a string.
|
|
func (b *base) Stacktrace() string {
|
|
if b.s == nil {
|
|
return ""
|
|
}
|
|
return b.s.String()
|
|
}
|
|
|
|
// WithStacktrace replaces the auto-captured stacktrace with a pre-formatted string
|
|
// and returns a new base error.
|
|
func (b *base) WithStacktrace(s string) *base {
|
|
return &base{
|
|
t: b.t,
|
|
c: b.c,
|
|
m: b.m,
|
|
e: b.e,
|
|
u: b.u,
|
|
a: b.a,
|
|
s: rawStacktrace(s),
|
|
r: b.r,
|
|
attrs: b.attrs,
|
|
}
|
|
}
|
|
|
|
// base implements Error interface.
|
|
func (b *base) Error() string {
|
|
if b.e != nil {
|
|
return b.e.Error()
|
|
}
|
|
|
|
return b.m
|
|
}
|
|
|
|
// New returns a base error. It requires type, code and message as input.
|
|
func New(t typ, code Code, message string) *base {
|
|
return &base{
|
|
t: t,
|
|
c: code,
|
|
m: message,
|
|
e: nil,
|
|
u: "",
|
|
a: []string{},
|
|
s: newStackTrace(),
|
|
}
|
|
}
|
|
|
|
// Newf returns a new base by formatting the error message with the supplied format specifier.
|
|
func Newf(t typ, code Code, format string, args ...any) *base {
|
|
return &base{
|
|
t: t,
|
|
c: code,
|
|
m: fmt.Sprintf(format, args...),
|
|
e: nil,
|
|
s: newStackTrace(),
|
|
}
|
|
}
|
|
|
|
// Wrapf returns a new error by formatting the error message with the supplied format specifier
|
|
// and wrapping another error with base.
|
|
func Wrapf(cause error, t typ, code Code, format string, args ...any) *base {
|
|
return &base{
|
|
t: t,
|
|
c: code,
|
|
m: fmt.Sprintf(format, args...),
|
|
e: cause,
|
|
s: newStackTrace(),
|
|
}
|
|
}
|
|
|
|
// Wrap returns a new error by wrapping another error with base.
|
|
func Wrap(cause error, t typ, code Code, message string) *base {
|
|
return &base{
|
|
t: t,
|
|
c: code,
|
|
m: message,
|
|
e: cause,
|
|
s: newStackTrace(),
|
|
}
|
|
}
|
|
|
|
// WithAdditionalf adds an additional error message to the existing error.
|
|
func WithAdditionalf(cause error, format string, args ...any) *base {
|
|
t, c, m, e, u, a, r, attrs := Unwrapb(cause)
|
|
var s fmt.Stringer
|
|
if original, ok := cause.(*base); ok {
|
|
s = original.s
|
|
}
|
|
b := &base{
|
|
t: t,
|
|
c: c,
|
|
m: m,
|
|
e: e,
|
|
u: u,
|
|
a: a,
|
|
s: s,
|
|
r: r,
|
|
attrs: attrs,
|
|
}
|
|
|
|
return b.WithAdditional(append(a, fmt.Sprintf(format, args...))...)
|
|
}
|
|
|
|
// WithUrl adds a url to the base error and returns a new base error.
|
|
func (b *base) WithUrl(u string) *base {
|
|
return &base{
|
|
t: b.t,
|
|
c: b.c,
|
|
m: b.m,
|
|
e: b.e,
|
|
u: u,
|
|
a: b.a,
|
|
s: b.s,
|
|
r: b.r,
|
|
attrs: b.attrs,
|
|
}
|
|
}
|
|
|
|
// WithAdditional adds additional messages to the base error and returns a new base error.
|
|
func (b *base) WithAdditional(a ...string) *base {
|
|
return &base{
|
|
t: b.t,
|
|
c: b.c,
|
|
m: b.m,
|
|
e: b.e,
|
|
u: b.u,
|
|
a: a,
|
|
s: b.s,
|
|
r: b.r,
|
|
attrs: b.attrs,
|
|
}
|
|
}
|
|
|
|
// WithRetry adds retry metadata to the base error and returns a new base error.
|
|
func (b *base) WithRetry(r retry) *base {
|
|
return &base{
|
|
t: b.t,
|
|
c: b.c,
|
|
m: b.m,
|
|
e: b.e,
|
|
u: b.u,
|
|
a: b.a,
|
|
s: b.s,
|
|
r: r,
|
|
attrs: b.attrs,
|
|
}
|
|
}
|
|
|
|
// setAttrs replaces the value at key with the given values and returns a new base error.
|
|
// Values are always stored as []string under a stable key.
|
|
// This helper is not exported; use WithSuggestions (or add a new With* method) instead.
|
|
func (b *base) setAttrs(key string, values ...string) *base {
|
|
copiedAttrs := make(map[string]any, len(b.attrs)+1)
|
|
maps.Copy(copiedAttrs, b.attrs)
|
|
|
|
replacement := make([]string, len(values))
|
|
copy(replacement, values)
|
|
copiedAttrs[key] = replacement
|
|
|
|
return &base{
|
|
t: b.t,
|
|
c: b.c,
|
|
m: b.m,
|
|
e: b.e,
|
|
u: b.u,
|
|
a: b.a,
|
|
s: b.s,
|
|
r: b.r,
|
|
attrs: copiedAttrs,
|
|
}
|
|
}
|
|
|
|
// addAttrs appends additional values to the existing list at key and returns a new base error.
|
|
// Values are always stored as []string under a stable key.
|
|
// This helper is not exported; use AddSuggestions, AddWarnings, or AddInvalidReferences,
|
|
// or add a new method that calls addAttrs for any specific attributes.
|
|
func (b *base) addAttrs(key string, values ...string) *base {
|
|
copiedAttrs := make(map[string]any, len(b.attrs)+1)
|
|
maps.Copy(copiedAttrs, b.attrs)
|
|
|
|
if len(values) > 0 {
|
|
existing, _ := copiedAttrs[key].([]string)
|
|
merged := make([]string, 0, len(existing)+len(values))
|
|
merged = append(merged, existing...)
|
|
merged = append(merged, values...)
|
|
copiedAttrs[key] = merged
|
|
}
|
|
|
|
return &base{
|
|
t: b.t,
|
|
c: b.c,
|
|
m: b.m,
|
|
e: b.e,
|
|
u: b.u,
|
|
a: b.a,
|
|
s: b.s,
|
|
r: b.r,
|
|
attrs: copiedAttrs,
|
|
}
|
|
}
|
|
|
|
// WithSuggestions replaces the list of suggestions on the base error.
|
|
func (b *base) WithSuggestions(suggestions ...string) *base {
|
|
return b.setAttrs("suggestions", suggestions...)
|
|
}
|
|
|
|
// AddSuggestions appends to the existing list of suggestions on the base error.
|
|
func (b *base) AddSuggestions(suggestions ...string) *base {
|
|
return b.addAttrs("suggestions", suggestions...)
|
|
}
|
|
|
|
// WithInvalidReference replaces the list of invalid references on the base error with a single entry.
|
|
func (b *base) WithInvalidReference(invalidReference string) *base {
|
|
return b.setAttrs("invalidReferences", invalidReference)
|
|
}
|
|
|
|
// WithInvalidReferences replaces the list of invalid references on the base error.
|
|
func (b *base) WithInvalidReferences(invalidReferences ...string) *base {
|
|
return b.setAttrs("invalidReferences", invalidReferences...)
|
|
}
|
|
|
|
// AddInvalidReference appends a single invalid reference to the existing list on the base error.
|
|
func (b *base) AddInvalidReference(invalidReference string) *base {
|
|
return b.addAttrs("invalidReferences", invalidReference)
|
|
}
|
|
|
|
// AddInvalidReferences appends to the existing list of invalid references on the base error.
|
|
func (b *base) AddInvalidReferences(invalidReferences ...string) *base {
|
|
return b.addAttrs("invalidReferences", invalidReferences...)
|
|
}
|
|
|
|
// WithWarnings replaces the list of warnings on the base error.
|
|
func (b *base) WithWarnings(warnings ...string) *base {
|
|
return b.setAttrs("warnings", warnings...)
|
|
}
|
|
|
|
// AddWarnings appends to the existing list of warnings on the base error.
|
|
func (b *base) AddWarnings(warnings ...string) *base {
|
|
return b.addAttrs("warnings", warnings...)
|
|
}
|
|
|
|
// WithRetryNever sets the retry policy to Never.
|
|
func (b *base) WithRetryNever() *base {
|
|
return b.WithRetry(retry{policy: RetryNever})
|
|
}
|
|
|
|
// WithRetryImmediate sets the retry policy to Immediate.
|
|
func (b *base) WithRetryImmediate() *base {
|
|
return b.WithRetry(retry{policy: RetryImmediate})
|
|
}
|
|
|
|
// WithRetryBackoff sets the retry policy to Backoff.
|
|
func (b *base) WithRetryBackoff() *base {
|
|
return b.WithRetry(retry{policy: RetryBackoff})
|
|
}
|
|
|
|
// WithRetryAfter sets the retry policy to After and requires a delay.
|
|
func (b *base) WithRetryAfter(delay time.Duration) *base {
|
|
return b.WithRetry(retry{policy: RetryAfter, after: delay})
|
|
}
|
|
|
|
// WithRetryAfterFix sets the retry policy to AfterFix.
|
|
func (b *base) WithRetryAfterFix() *base {
|
|
return b.WithRetry(retry{policy: RetryAfterFix})
|
|
}
|
|
|
|
// WithRetryAfterAuth sets the retry policy to AfterAuth.
|
|
func (b *base) WithRetryAfterAuth() *base {
|
|
return b.WithRetry(retry{policy: RetryAfterAuth})
|
|
}
|
|
|
|
// Unwrapb is a combination of built-in errors.As and type casting.
|
|
// It finds the first error in cause that matches base,
|
|
// and if one is found, returns the individual fields of base.
|
|
// Otherwise, it returns TypeInternal, the original error string
|
|
// and the error itself.
|
|
//
|
|
//nolint:staticcheck // ST1008: intentional return order matching struct field order (TCMEUARA)
|
|
func Unwrapb(cause error) (typ, Code, string, error, string, []string, retry, map[string]any) {
|
|
base, ok := cause.(*base)
|
|
if ok {
|
|
return base.t, base.c, base.m, base.e, base.u, base.a, base.r, base.attrs
|
|
}
|
|
|
|
return TypeInternal, CodeUnknown, cause.Error(), cause, "", []string{}, retry{policy: RetryNever}, map[string]any{}
|
|
}
|
|
|
|
// Ast checks if the provided error matches the specified custom error type.
|
|
func Ast(cause error, typ typ) bool {
|
|
t, _, _, _, _, _, _, _ := Unwrapb(cause)
|
|
|
|
return t == typ
|
|
}
|
|
|
|
// Asc checks if the provided error matches the specified custom error code.
|
|
func Asc(cause error, code Code) bool {
|
|
_, c, _, _, _, _, _, _ := Unwrapb(cause)
|
|
|
|
return c.s == code.s
|
|
}
|
|
|
|
// Join is a wrapper around errors.Join.
|
|
func Join(errs ...error) error {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
// As is a wrapper around errors.As.
|
|
func As(err error, target any) bool {
|
|
return errors.As(err, target)
|
|
}
|
|
|
|
// Is is a wrapper around errors.Is.
|
|
func Is(err error, target error) bool {
|
|
return errors.Is(err, target)
|
|
}
|
|
|
|
// WrapNotFoundf is a wrapper around Wrapf with TypeNotFound.
|
|
func WrapNotFoundf(cause error, code Code, format string, args ...any) *base {
|
|
return Wrapf(cause, TypeNotFound, code, format, args...).WithRetry(retry{policy: RetryNever})
|
|
}
|
|
|
|
// NewNotFoundf is a wrapper around Newf with TypeNotFound.
|
|
func NewNotFoundf(code Code, format string, args ...any) *base {
|
|
return Newf(TypeNotFound, code, format, args...).WithRetry(retry{policy: RetryNever})
|
|
}
|
|
|
|
// WrapInternalf is a wrapper around Wrapf with TypeInternal.
|
|
func WrapInternalf(cause error, code Code, format string, args ...any) *base {
|
|
return Wrapf(cause, TypeInternal, code, format, args...).WithRetry(retry{policy: RetryNever})
|
|
}
|
|
|
|
// NewInternalf is a wrapper around Newf with TypeInternal.
|
|
func NewInternalf(code Code, format string, args ...any) *base {
|
|
return Newf(TypeInternal, code, format, args...).WithRetry(retry{policy: RetryNever})
|
|
}
|
|
|
|
// WrapInvalidInputf is a wrapper around Wrapf with TypeInvalidInput.
|
|
func WrapInvalidInputf(cause error, code Code, format string, args ...any) *base {
|
|
return Wrapf(cause, TypeInvalidInput, code, format, args...).WithRetry(retry{policy: RetryAfterFix})
|
|
}
|
|
|
|
// NewInvalidInputf is a wrapper around Newf with TypeInvalidInput.
|
|
func NewInvalidInputf(code Code, format string, args ...any) *base {
|
|
return Newf(TypeInvalidInput, code, format, args...).WithRetry(retry{policy: RetryAfterFix})
|
|
}
|
|
|
|
// NewMethodNotAllowedf is a wrapper around Newf with TypeMethodNotAllowed.
|
|
func NewMethodNotAllowedf(code Code, format string, args ...any) *base {
|
|
return Newf(TypeMethodNotAllowed, code, format, args...).WithRetry(retry{policy: RetryNever})
|
|
}
|
|
|
|
// WrapTimeoutf is a wrapper around Wrapf with TypeTimeout.
|
|
func WrapTimeoutf(cause error, code Code, format string, args ...any) *base {
|
|
return Wrapf(cause, TypeTimeout, code, format, args...).WithRetry(retry{policy: RetryBackoff})
|
|
}
|
|
|
|
// NewTimeoutf is a wrapper around Newf with TypeTimeout.
|
|
func NewTimeoutf(code Code, format string, args ...any) *base {
|
|
return Newf(TypeTimeout, code, format, args...).WithRetry(retry{policy: RetryBackoff})
|
|
}
|
|
|
|
// NewUnauthenticatedf is a wrapper around Newf with TypeUnauthenticated.
|
|
func NewUnauthenticatedf(code Code, format string, args ...any) *base {
|
|
return Newf(TypeUnauthenticated, code, format, args...).WithRetry(retry{policy: RetryAfterAuth})
|
|
}
|
|
|
|
// NewForbiddenf is a wrapper around Newf with TypeForbidden.
|
|
func NewForbiddenf(code Code, format string, args ...any) *base {
|
|
return Newf(TypeForbidden, code, format, args...).WithRetry(retry{policy: RetryNever})
|
|
}
|
|
|
|
// Attr returns an slog.Attr with a standardized "exception" key for the given error.
|
|
func Attr(err error) slog.Attr {
|
|
return slog.Any("exception", err)
|
|
}
|
|
|
|
// TypeAttr returns an OTel attribute.KeyValue with the "error.type" semconv key
|
|
// set to the error's type string.
|
|
func TypeAttr(err error) attribute.KeyValue {
|
|
t, _, _, _, _, _, _, _ := Unwrapb(err)
|
|
return attribute.String("error.type", t.String())
|
|
}
|
|
|
|
func RetryAfterOf(err error) (delay time.Duration, ok bool) {
|
|
_, _, _, _, _, _, r, _ := Unwrapb(err)
|
|
delay = r.after
|
|
|
|
return delay, r.policy == RetryAfter
|
|
}
|