Kinds and interfaces¶
The three interfaces a layer can implement, the eight kind constants this package defines, and the one exported type.
Nothing in this package type-switches on its own concrete types. Every reader reaches a layer through the interfaces below, which is what lets a layer the package has never seen — a decoded wire layer, or a consumer's own error type — travel the chain intact and contribute correctly.
Kinder¶
Gives a layer a stable identity, used to route it: to a wire codec, or to a telemetry attribute.
The string is a serialisation key
Once a kind has crossed a process boundary, changing it breaks decoders that have not been updated. Treat these as you would a protobuf field number.
Payloader¶
Exposes what a layer carries, for anything that needs to move or record it rather than print it.
The concrete payload type is per-kind — see the table below. A reader that does not recognise a kind must ignore the payload rather than assume a shape.
StackTracer¶
Implemented by any layer carrying a stack.
It is separate from Kinder/Payloader on purpose: those route a layer by kind,
whereas a stack is a capability several kinds have. A leaf from New carries
one and so does a WithStack wrapper, but they are different kinds with
different payloads.
Implementing it is how a consumer's own error type, or a decoded wire layer,
contributes a stack to StackOf.
The kind constants¶
const (
KindBasic = "errors.basic"
KindSentinel = "errors.sentinel"
KindMessage = "errors.message"
KindStack = "errors.stack"
KindHint = "errors.hint"
KindDetail = "errors.detail"
KindAttrs = "errors.attrs"
KindJoin = "errors.join"
)
They are exported because a wire codec registers against them and telemetry reports them.
| Constant | Value | Payload type | Structural? |
|---|---|---|---|
KindBasic |
errors.basic |
string — the message |
no |
KindSentinel |
errors.sentinel |
string — the message |
no |
KindMessage |
errors.message |
string — the prefix |
yes |
KindStack |
errors.stack |
StackTrace |
yes |
KindHint |
errors.hint |
string |
yes |
KindDetail |
errors.detail |
string |
yes |
KindAttrs |
errors.attrs |
[]slog.Attr |
yes |
KindJoin |
errors.join |
[]error |
no |
Structural means the kind describes how an error was annotated rather than
what it is, and so is skipped by KindOf when working out
an error's identity.
Where basic and sentinel come from¶
Both are produced by the same internal leaf type, and which kind it reports depends on how it was made:
Newcaptures a stack, so the leaf reportserrors.basic.NewSentinelcaptures no stack and is given an explicit kind, so the leaf reports that kind — noterrors.sentinel.
errors.sentinel is therefore what a stackless leaf reports when it was given no
kind of its own — which happens when you pass an empty kind:
errors.KindOf(errors.NewSentinel("forge.x", "m")) // "forge.x"
errors.KindOf(errors.NewSentinel("", "m")) // "errors.sentinel"
errors.KindOf(errors.New("m")) // "errors.basic"
An empty kind gives up the property the constructor exists for, since
errors.sentinel is shared by every such error and cannot identify yours after a
wire crossing. Namespace it.
StackTrace¶
A captured call stack, bounded at 32 frames.
It is exported and renderable because go/observability needs it for OTEL's
exception.stacktrace — a private field that a formatter happened to reach would
not serve that.
String() renders the conventional Go form, alternating a function line with a
tab-indented file:line:
That is what OTEL expects, and what a reader of a Go panic already knows how to scan.