[Go to site: main page, start]

Skip to content

What this package does not do

Deliberate absences and known constraints. Each is a decision rather than a gap waiting to be filled, except where it says otherwise.

No OpenTelemetry, and no RecordError

This package ships nothing that talks to OTEL.

Deciding an error is worth recording happens where a span is in scope, not where the error is made. That belongs to gitlab.com/phpboyscout/go/observability, which reaches what it needs through StackOf, KindOf and Attrs — the reason those are exported and renderable.

No wire serialisation — yet

There is no encoder or decoder here. A sibling module is planned, keyed on ErrorKind, which is why kinds are stable strings and why the package documents them as serialisation keys.

Until it exists, an error that crosses a process boundary arrives as whatever the transport made of it, and its identity has to be re-established by the receiver.

No dependencies, and that constrains the API

The package imports only the standard library, asserted by depfootprint_test.go rather than left to review. An error package is imported by every module in the estate, so it should be the lightest thing in the graph.

The visible consequence is WithAttrs taking []slog.Attr rather than a richer type of its own.

Stacks are bounded at 32 frames

Deep enough for any real call chain, shallow enough that an error on a hot path is not expensive. A chain deeper than that is truncated, and there is no option to raise the bound.

Errors are immutable once constructed

Every annotator returns a new error wrapping the old one. Nothing hands back a mutable map for you to add to later, which is how tozd/go/errors does it — mutating an error after construction invites aliasing and races.

A sentinel carries no stack until you add one

NewSentinel captures no stack by design, because at package scope the call site is initialisation. If you return a sentinel directly, the error has no stack at all. Wrap it:

return errors.WithStack(ErrNotFound)

Is compares pointers, which do not survive a wire

Is matches on identity, so a sentinel decoded from a transport is not the same instance as the one in your binary and will not match. The stable kind is what a future codec will use to re-establish identity.

A leaf from Newf has no kind

KindOf returns "" for an error built from New, Newf or Errorf that does not wrap a sentinel, and a slog record of it therefore has no kind field. This is a consequence of identity being something you declare, not something inferred from a message — but it does mean an error you want to alert on needs a sentinel at its leaf.

Hints and details de-duplicate; attributes do not

Hints and Details collapse identical strings to one. Attrs keeps every attribute, including repeated keys, because which one wins is the caller's decision.

If you attach the same hint at three levels you will see it once; the same attribute key at three levels appears three times.

Nothing here reports on an error's absence

There is no "assert this error was handled" tooling, no linting of unwrapped returns, and no runtime warning for an error that was constructed and dropped. That is the job of errcheck and the rest of the lint suite.