Skip to content

Render errors cleanly for both CLIs and structured logs#28

Merged
TeddyAndrieux merged 3 commits into
mainfrom
feature/properly-handle-logger
Jun 4, 2026
Merged

Render errors cleanly for both CLIs and structured logs#28
TeddyAndrieux merged 3 commits into
mainfrom
feature/properly-handle-logger

Conversation

@TeddyAndrieux
Copy link
Copy Markdown
Contributor

Error() baked the stack trace into its output, so every human-facing error (CLI, %w chains, err.Error() logs) carried a noisy at=[(func=..., file=..., line=...)] tail — with no way to get the plain message. And under slog the two handlers disagreed: the JSON handler falls back to Error() while the text handler uses %+v, so text logs printed JSON and JSON logs printed the bare string.

This PR splits the two audiences cleanly:

  • Error() now returns only title, identifier, details, properties and cause — no stack. The full dump stays on %+v. The empty (identifier) segment is dropped too.
  • *Error implements slog.LogValuer: both handlers now resolve the same structured group (incl. the stack), so JSON logs get queryable fields and text logs stay consistent.
  • Trace.String() renders a frame as :, so the stack reads cleanly under the text handler instead of as pointer addresses.

@TeddyAndrieux TeddyAndrieux requested a review from a team as a code owner June 4, 2026 08:37
@TeddyAndrieux TeddyAndrieux force-pushed the feature/properly-handle-logger branch from ef78556 to 75d3134 Compare June 4, 2026 08:48
Error() (and the %s/%q verbs) is the message form: it feeds CLI output,
%w chains and any logger that prints err.Error(). Baking the stack into
it left no way to get the plain message and produced noisy
"... at=[(func='...', file='...', line='...')]" tails wherever an error
was shown to a human.

Error() now carries only title, identifier, details, properties and
cause. The full message-plus-stack dump stays available through the %+v
verb (unchanged). The "(identifier)" segment is also omitted when no
identifier is set, so untyped errors read "title: detail" instead of
"title (): detail".

Note: this changes the string returned by Error(); callers that parsed
it for the stack must switch to %+v.
slog's handlers serialise an error value differently: the JSON handler
falls back to err.Error() (a string) while the text handler uses %+v
(JSON). Combined with go-errors' formatting that inverts the output —
text logs show JSON, JSON logs show the bare message.

Implement slog.LogValuer on *Error so both handlers resolve the same
structured group: title, identifier, details, properties, cause and the
stack. JSON consumers get queryable fields (the stack as an array of
{function,file,line} objects); the text handler renders each frame via
the new Trace.String(), so the stack reads as "<func> <file>:<line>"
instead of pointer addresses.

The stack is part of the log form (post-mortem debugging) but stays out
of Error() (the human/CLI form): the two audiences are served on purpose.
@TeddyAndrieux TeddyAndrieux force-pushed the feature/properly-handle-logger branch from 75d3134 to 4ac88af Compare June 4, 2026 09:12
@TeddyAndrieux TeddyAndrieux merged commit ee07d3e into main Jun 4, 2026
2 checks passed
@TeddyAndrieux TeddyAndrieux deleted the feature/properly-handle-logger branch June 4, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants