Formatting and logging¶
What each verb prints, and what a slog handler receives.
Formatting is implemented once and reached by every wrapper — the wrappers hold no formatting logic of their own, so a layer this package has never seen prints correctly too.
The verbs¶
Given this error:
err := errors.WithAttrs(
errors.WithDetail(
errors.WithHint(
errors.Wrap(ErrNotFound, "loading codeberg"),
"Register it first.",
),
"checked 3 registries",
),
slog.String("host", "codeberg.org"),
)
| Verb | Output |
|---|---|
%s |
loading codeberg: provider not found |
%v |
loading codeberg: provider not found |
%q |
"loading codeberg: provider not found" |
%+v |
everything — see below |
%s, %v and %q all render the message alone. Annotations never change the
message; they travel alongside it.
%+v¶
loading codeberg: provider not found
HINT: Register it first.
DETAIL: checked 3 registries
host=codeberg.org
main.main
/home/you/project/main.go:74
runtime.main
/usr/local/go/src/runtime/proc.go:290
runtime.goexit
/usr/local/go/src/runtime/asm_amd64.s:1771
The order is deliberate — remediation first, because it is the actionable part, then operator context, then the structured attributes, then the stack:
- the message
- every hint, each on its own line prefixed
HINT: - every detail, prefixed
DETAIL: - every attribute, as
key=value - the outermost stack
A section is absent entirely when the error carries nothing of that kind.
slog¶
Errors implement slog.LogValuer, so they render as a group rather than a
flattened string:
level=ERROR msg="release lookup failed" err.msg="loading codeberg: provider not found" \
err.kind=forge.not_found err.hint="[Register it first.]" \
err.detail="[checked 3 registries]" err.host=codeberg.org
The group contains:
| Field | Present when | Value |
|---|---|---|
msg |
always | err.Error() |
kind |
KindOf is non-empty |
the error's identity |
hint |
there is at least one hint | the list of hints |
detail |
there is at least one detail | the list of details |
| your keys | attributes were attached | each WithAttrs attribute, flattened into the group |
Two consequences worth planning for:
kind is omitted when nothing declares one. An error whose leaf came from
New, Newf or Errorf and which wraps no sentinel has no identity, so there is
no kind field to query on. If you want to alert on a failure class, give the
leaf a NewSentinel.
hint and detail are lists, and render with brackets — hint="[Register it
first.]" even for a single hint, because the value is a []string.
The stack is not in the log record¶
That is deliberate. It is large, it is rarely what a log line is for, and it
remains available to a handler that wants it through
StackOf — which is also how go/observability reaches it
for a span.