raised

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

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

View Source
var (
	ErrValidation  = NewSentinel("failed validation")
	ErrInvalidHash = NewSentinel("invalid hash function")
)

Functions

func UnwrapTerminal

func UnwrapTerminal[T any](err error) error

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

func Trace(err error, msg string) Error

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

func Tracef(err error, msg string, args ...any) Error

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

type HashFunc = func() hash.Hash

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

func NewSentinelError[T any](msg string) *Sentinel[T]

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]) Code

func (self *Sentinel[T]) Code() int

func (*Sentinel[T]) Error

func (self *Sentinel[T]) Error() string

func (*Sentinel[T]) Fingerprint

func (self *Sentinel[T]) Fingerprint() string

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.

Jump to

Keyboard shortcuts

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