Module github.com/thanhminhmr/go-exception, Go 1.25, MPL-2.0. Optional zerolog integration (disabled with the
no_zerolog build tag).
Exception is a sealed interface (exception.go:32): a private __() method prevents any external implementation.
All methods use value receivers; mutation methods return a new Exception rather than mutating in place.
Documented rule: always use the returned value — never assume the original is unchanged.
The interface has three concrete types, chosen by weight (cheapest sufficient form wins):
type String string. Carries only type + message, parsed by the ": " separator (string.go:15):
- Missing separator ⇒ whole string is the type, message is empty.
Error()drops the separator when type or message is empty (e.g."IOError: msg"→"IOError: msg";": msg"→"msg";"IOError:"→"IOError").- Usable as a
const(e.g.const ErrRead = exception.String("IOError: read failed")). - All field getters (
GetCause,GetSuppressed,GetRecovered,GetStackTrace,GetExtras,GetExtra) return zero values;Clone()returns itself (immutable).
type multipleErrors []error. Empty type and message; GetCause() and Unwrap() []error both return itself. Error()
is fmt.Sprintf("%v", []error(e)). Produced by Join.
Struct with Type, Message, Cause (multipleErrors), Suppressed (multipleErrors), Recovered any,
StackTrace StackFrames, Extras map[string]any. Error() returns "Type: Message", or whichever of type/message is
non-empty, or "" if both empty. Unwrap() []error returns Cause.
String and multipleErrors stay cheap until enriched, then promote to fullException carrying over their
existing data:
Op on String |
Op on multipleErrors |
Result |
|---|---|---|
AddCause (non-empty) |
AddCause (non-empty) |
fullException with Cause set, type preserved (String) or causes preserved (multipleErrors) |
AddSuppressed (non-empty) |
AddSuppressed (non-empty) |
fullException with Suppressed set |
SetRecovered(non-nil) |
SetRecovered(non-nil) |
fullException with Recovered set |
FillStackTrace |
FillStackTrace |
fullException with StackTrace set |
SetExtras(non-nil) |
SetExtras(non-nil) |
fullException with Extras set |
SetExtra(key, non-nil) |
SetExtra(key, non-nil) |
fullException with a fresh Extras map containing only that key |
SetMessage (non-empty) |
SetMessage (non-empty) |
String returns a new String; multipleErrors promotes to fullException keeping Cause |
Once a fullException, all further mutations stay on fullException (struct is copied and the field is reassigned).
Combines errors into a multipleErrors. Nil inputs are filtered. Nested multipleErrors are flattened
(auto-unboxed). Returns nil if nothing remains.
type Template string. Format(args...) returns String(fmt.Sprintf(t, args...)). Reusable constant format for
exception messages.
PanicError = String("panicked")— the type used for panic-originated exceptions.Panic(v): ifvis already anExceptionwhose type isPanicError, re-panics it unchanged (enables chained recover handlers without altering state). Otherwise wrapsvasfullException{Type: "panicked", Recovered: v, StackTrace: StackTrace(1)}and panics it.Recover(callback): no-op when no panic occurred. If the recovered value is anExceptionwith typePanicError, passes it to the callback unchanged. Otherwise builds afullExceptionand strips leadingruntime.*panic*frames from the trace (panic.go:87-97) so the trace starts at the real panic site.Recover(nil)panics with"BUG: callback is nil". Intended fordefer exception.Recover(func(ex exception.Exception) { ... }).
- Cause (
GetCause/AddCause): the root errors that led to this exception. OnfullException,AddCauseappends viamultipleErrors.append(multiple_errors.go:156) which filters nils and flattens nestedmultipleErrors. - Suppressed (
GetSuppressed/AddSuppressed): errors intentionally ignored or deferred while handling this exception. Same append semantics as Cause. - Recovered (
GetRecovered/SetRecovered): the value captured from a panic. - StackTrace (
GetStackTrace/FillStackTrace):StackFrame{Function, File, Line}slice.FillStackTrace(skip)callsStackTrace(skip+1)so it captures from the caller ofFillStackTrace;skip=0includes that caller.StackTracecaptures up to 64 PCs viaruntime.Callers(2+skip, …)and appends every frame returned byruntime.CallersFrames.Next, including the last (wheremore==false). - Extras (
GetExtras/SetExtras/GetExtra/SetExtra): arbitrary key-value metadata.SetExtralazily allocates the map on first write;SetExtra(key, nil)deletes the key (no-op on a nil map, sincedeleteon nil is allowed in Go).
errors.Iswalks the cause chain via multi-Unwrap() []error:fullException.Unwrap()returnsCause,multipleErrors.Unwrap()returns itself. AJoinresult therefore matches any of its members.errors.Asworks through the same Unwrap chain.
String.Clone()→ returns itself (immutable).multipleErrors.Clone()→slices.Clone(new slice, shared error elements).fullException.Clone()→ new struct withslices.Clonefor Cause/Suppressed/StackTrace andmaps.Clonefor Extras. Referenced values (error elements, the recovered value) are not deeply cloned — they're shared between original and clone.
AddCause/AddSuppressed/Joinsilently filter all nil error arguments.- On
String/multipleErrors:SetRecovered(nil),SetExtras(nil), andSetExtra(key, nil)are no-ops returning the receiver unchanged (no promotion). GetExtraon an exception with no extras returns(nil, false).
zerolog.go compiles unless the no_zerolog build tag is set. String, fullException, multipleErrors,
StackFrame, and StackFrames all implement MarshalZerologObject / MarshalZerologArray, omitting empty fields and
choosing AnErr (single) vs Errs (multiple) based on slice length.