Documentation
¶
Overview ¶
Package raised provides structured error propagation, tracing, and stable error identity for Go programs.
Overview ¶
Standard Go errors carry no information about how they propagated through the call stack. raised addresses this by wrapping errors in a trace that records each propagation step — the call site file, line, and an optional message — as the error travels up the call stack.
raised also provides typed sentinel errors scoped to a package, and a stable hashing mechanism that identifies errors by their propagation path and root cause, independently of any dynamic context embedded in error messages.
Sentinels ¶
Sentinel errors are package-level error constants declared with NewSentinel or NewSentinelError. The phantom type parameter T scopes the sentinel to the declaring package, preventing accidental errors.Is matches across packages. An optional ERROR(n) prefix embeds a numeric code for lightweight classification:
type pkg struct{}
var (
ErrNotFound = raised.NewSentinelError[pkg]("ERROR(1) not found")
ErrBadRequest = raised.NewSentinelError[pkg]("ERROR(2) bad request")
)
Tracing ¶
Trace wraps any error in a propagation trace. Each call records the call site and an optional message. If the error is already a raised Error it is extended in place; otherwise a new trace is created with the error as its root cause:
func readConfig(path string) error {
f, err := os.Open(path)
if err != nil {
return raised.Trace(err, "open config")
}
// ...
}
func loadApp() error {
if err := readConfig("app.yaml"); err != nil {
return raised.Trace(err, "load app")
}
// ...
}
The full traceback is available via the Trace() method or %+v formatting:
if err := loadApp(); err != nil {
fmt.Printf("%+v\n", err)
}
Classification ¶
When a package receives a foreign error it can assert its own sentinel identity without changing the underlying cause, using Classify:
func fetchUser(id string) error {
user, err := db.Get(id)
if err != nil {
err = raised.Trace(err, "fetch user")
err.Classify(ErrNotFound)
return err
}
// ...
}
if errors.Is(err, ErrNotFound) {
// true, regardless of the underlying db error
}
Error identity and keying ¶
An ErrorKeyer computes a stable ErrorKey for a raised Error, derived from its propagation path and terminal root cause. Two errors sharing the same ErrorKey represent the same problem: identical code path and equivalent root cause. This is useful for error aggregation, deduplication, and monitoring.
An ErrorKeyer is scoped to the sentinel family T, consistent with the phantom type used for sentinel declaration:
type pkg struct {}
var Keyer, _ = raised.NewErrorKeyer[pkg](nil)
func handle(err error) {
key, ok := Keyer.Key(err)
if ok {
monitor.Record(key)
}
}
The ErrorKey is stable across process restarts and hosts as long as the source code has not changed — it is derived from file/line strings rather than runtime memory addresses.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrValidation = NewSentinel("failed validation") ErrInvalidHash = NewSentinel("invalid hash function") )
Functions ¶
func UnwrapTerminal ¶
UnwrapTerminal returns "minimal" error obtained by recursively unwrapping err or casting err to Sentinel[T], SentinelError...
Types ¶
type Error ¶
type Error interface {
error
// Cause returns the root error that initiated this trace,
// typically a SentinelError or a well-typed external error.
Cause() error
// Trace returns a formatted traceback string listing each Trace call
// site in order from most recent to oldest, including file and line
// information. Use %+v with fmt to obtain the same output.
Trace() string
// Classify overrides the sentinel identity of this error from the caller's
// perspective. Intended for packages that receive a foreign error and want
// to assert their own interpretation without changing the underlying cause.
//
// Example:
//
// err = raised.Trace(err, "storage unavailable")
// err.Classify(ErrServiceUnavailable)
Classify(SentinelError)
}
Error extends the standard error interface with propagation tracing and structural comparison. Instances are created exclusively via Trace.
func Trace ¶
Trace records a propagation step for err, attaching msg and the call site PC to the trace. If err is already a traced error it is extended in place, otherwise a new trace is created with err as its cause. Returns nil if err is nil.
func Tracef ¶
Tracef records a propagation step for err, attaching msg and the call site PC to the trace. If err is already a traced error it is extended in place, otherwise a new trace is created with err as its cause. If args are provided, msg is used as a format string. Returns nil if err is nil.
type ErrorKey ¶
type ErrorKey = [keySize]byte
ErrorKey is a fixed-size hash derived from an error's propagation path and terminal error identity. Two errors sharing the same ErrorKey represent the same problem: identical code path and equivalent root cause.
type ErrorKeyer ¶
type ErrorKeyer interface {
// Key returns an ErrorKey and a bool indicating if the key could be determined.
Key(error) (ErrorKey, bool)
// contains filtered or unexported methods
}
ErrorKeyer computes a stable ErrorKey for a raised Error. The key is derived from the error's propagation path and terminal root cause, independently of any dynamic context embedded in error messages. Key returns true only when a key could be determined.
func NewErrorKeyer ¶
func NewErrorKeyer[T any](ukl UnstableKeyListener) (ErrorKeyer, error)
NewErrorKeyer returns an ErrorKeyer using SHA256 as the default hash function, scoped to the sentinel family identified by the phantom type T.
func NewSentinelErrorKeyer ¶
func NewSentinelErrorKeyer[T any](hf HashFunc, ukl UnstableKeyListener) (ErrorKeyer, error)
NewSentinelErrorKeyer returns a ErrorKeyer scoped to the sentinel family identified by the phantom type T, using hf as the hash function. Returns ErrInvalidHash if hf is nil or produces fewer than keySize bytes.
type HashFunc ¶
HashFunc is a factory function returning a new hash.Hash instance. The hash must produce at least keySize bytes.
type L1Key ¶
type L1Key = [2 + traceSize]uintptr
L1Key is a stable identifier for an error's propagation path, derived from the module entry point, the recorded propagation PCs, and the total Trace call count.
type Sentinel ¶
type Sentinel[T any] struct { // contains filtered or unexported fields }
Sentinel is a generic error value intended for package-level declaration. The type parameter T is a phantom type that lets each package define a distinct sentinel family, preventing accidental cross-package errors.Is matches between sentinels that share the same message and code.
func NewSentinelError ¶
NewSentinelError creates a *Sentinel[T] for package-level error declaration. If msg begins with ERROR(n), the integer n is extracted as the sentinel's Code and the prefix is normalised, stripping any underscore separators from n. Should only be called at package initialisation time (var declarations).
func (*Sentinel[T]) Fingerprint ¶
type SentinelError ¶
type SentinelError interface {
error
// Code returns the numeric identifier embedded in the sentinel message
// via the ERROR(n) prefix, or 0 if no such prefix was present.
Code() int
// Fingerprint returns a representation of the error suitable for hashing.
Fingerprint() string
// contains filtered or unexported methods
}
SentinelError is the interface implemented by package-level error values. It extends error with a numeric Code, enabling lightweight classification and comparison without relying on message strings.
func NewSentinel ¶
func NewSentinel(msg string) SentinelError
NewSentinel creates a SentinelError using the default phantom type. Prefer NewSentinelError when the calling package wants a distinct sentinel family. Should only be called at package initialisation time (var declarations).
type UnstableKeyEvent ¶
type UnstableKeyEvent struct {
// Error is the full raised Error for which a stable key could not be determined.
Error Error
// K1 is the stable propagation path key for Error.
// K1 remains constant for a given code path and can be used by the listener to
// track instability frequency per origin site.
K1 L1Key
// Key is the ErrorKey that was derived for Error on this call.
// It may differ across calls originating from the same code path.
Key ErrorKey
// EntryPoint is the program counter of the module entry site for Error.
// This is the recommended location at which to call Classify in order to
// assert a stable sentinel identity and resolve the instability.
EntryPoint uintptr
}
UnstableKeyEvent is delivered to an UnstableKeyListener when the ErrorKey for a given code path could not be stably determined, indicating that the terminal cause varies across calls originating from the same location. This typically occurs when a foreign error embeds transient state — such as a request ID or a dynamic value — in its message, preventing key stabilisation.
The most efficient fix is to call Classify on the propagating Error at the EntryPoint location, asserting a stable sentinel identity that overrides the unstable foreign cause.
type UnstableKeyListener ¶
type UnstableKeyListener interface {
// OnUnstableKey is called each time an ErrorKey fluctuates for a given
// code path, with the associated event.
OnUnstableKey(UnstableKeyEvent)
}
UnstableKeyListener is implemented by types that wish to observe key instability.
type UnstableKeyListenerFunc ¶
type UnstableKeyListenerFunc func(UnstableKeyEvent)
UnstableKeyListenerFunc is an adapter type to allow the use of ordinary functions as UnstableKeyListener.
func (UnstableKeyListenerFunc) OnUnstableKey ¶
func (self UnstableKeyListenerFunc) OnUnstableKey(evt UnstableKeyEvent)
OnUnstableKey calls self.
type UnstableKeyLogger ¶
type UnstableKeyLogger struct {
// contains filtered or unexported fields
}
UnstableKeyLogger is an UnstableKeyListener that logs a diagnostic when an error key fluctuates for a given code path, rate-limited by a per-entry-point cooldown and a per-path event threshold.
func NewUnstableKeyLogger ¶
func NewUnstableKeyLogger(period time.Duration, threshold int64, level slog.Level, logger *slog.Logger) (*UnstableKeyLogger, error)
NewUnstableKeyLogger returns an UnstableKeyLogger that emits notifications at the given level via logger, suppressing events until threshold instabilities have been observed on a given code path beyond the mandatory first cache-miss, and then at most once per period per entry point. Returns ErrValidation if period < minPeriod or threshold < 0. If logger is nil, slog.Default() is used.
func (*UnstableKeyLogger) OnUnstableKey ¶
func (self *UnstableKeyLogger) OnUnstableKey(evt UnstableKeyEvent)
OnUnstableKey implements UnstableKeyListener. It increments the event counter for evt.K1 and, once the threshold is exceeded, emits a rate-limited log record identifying the entry point at which Classify should be called. No-ops if evt.K1 is zero or evt.EntryPoint is zero.