Attach a hint¶
A message says what went wrong. A hint says what to do about it.
return errors.WithHint(
errors.New("configuration uses auth.env, which is no longer read"),
"Move the value to auth.value, or set the environment variable directly.",
)
Why they are separate¶
The two have different audiences and different lifetimes. A message is what gets logged, matched and wrapped as the error travels. A hint is what a CLI prints to a human at the edge, once, when it decides to give up.
Keeping them apart means a library can offer guidance without a caller being forced to render it, and a caller can render guidance without parsing it back out of a sentence.
fmt.Println(err) // configuration uses auth.env, which is no longer read
errors.Hints(err) // [Move the value to auth.value, or set …]
Formatting¶
More than one¶
Hints accumulate as an error travels, outermost first, and identical ones collapse:
inner := errors.WithHint(errors.New("root"), "check the token")
outer := errors.WithHint(errors.Wrap(inner, "loading"), "run `mytool init`")
errors.Hints(outer) // [run `mytool init`, check the token]
errors.FlattenHints(outer) // "run `mytool init`\n--\ncheck the token"
Outermost first is deliberate: the layer closest to the user knows most about what they were trying to do.
Hints, details and attributes¶
| Use | For | Audience |
|---|---|---|
WithHint |
what to do about it | the user |
WithDetail |
context that is prose | an operator reading verbose output |
WithAttrs |
context that is data | a query over logs or traces |
See Hints, details and attributes for where the line sits.
Writing a good one¶
State the action, not the diagnosis. The message already has the diagnosis.
// weak — restates the problem
"The configuration key auth.env is not read."
// better — tells them what to change
"Move the value to auth.value, or set GITEA_TOKEN directly."
Name the concrete thing where you can: the key, the file, the command. A hint that could apply to any error is not doing more than the message.