| # Error Printing — Draft Design | |
| Marcel van Lohuizen\ | |
| August 27, 2018 | |
| ## Abstract | |
| This document is a draft design for additions to the errors package | |
| to define defaults for formatting error messages, | |
| with the aim of making formatting of different error message implementations interoperable. | |
| This includes the printing of detailed information, | |
| stack traces or other position information, localization, and limitations of ordering. | |
| For more context, see the [error values problem overview](go2draft-error-values-overview.md). | |
| ## Background | |
| It is common in Go to build your own error type. | |
| Applications can define their own local types or use | |
| one of the many packages that are available for defining errors. | |
| Broadly speaking, errors serve several audiences: | |
| programs, users, and diagnosers. | |
| Programs may need to make decisions based on the value of errors. | |
| This need is addressed in [the error values draft designs](go2draft-error-values-overview.md). | |
| Users need a general idea of what went wrong. | |
| Diagnosers may require more detailed information. | |
| This draft design focuses on providing legible error printing | |
| to be read by people—users and diagnosers—not programs. | |
| When wrapping one error in context to produce a new error, | |
| some error packages distinguish between opaque and transparent | |
| wrappings, which affect whether error inspection is allowed to | |
| see the original error. | |
| This is a valid distinction. | |
| Even if the original error is hidden from programs, however, | |
| it should typically still be shown to people. | |
| Error printing therefore must use an interface method | |
| distinct from the common “next in error chain” methods | |
| like `Cause`, `Reason`, or the error inspection draft design’s `Unwrap`. | |
| There are several packages that have attempted to provide common error interfaces. | |
| These packages typically do not interoperate well with each other or with bespoke error implementations. | |
| Although the interfaces they define are similar, there are implicit assumptions that lead to poor interoperability. | |
| ## Design | |
| This design focuses on printing errors legibly, for people to read. | |
| This includes possible stack trace information, | |
| a consistent ordering, | |
| and consistent handling of formatting verbs. | |
| ### Error detail | |
| The design allows for an error message to include additional detail | |
| printed upon request, by using special formatting verb `%+v`. | |
| This detail may include stack traces or other detailed information | |
| that would be reasonable to elide in a shorter display. | |
| Of course, many existing error implementations only have | |
| a short display, and we don’t expect them to change. | |
| But implementations that do track additional detail | |
| will now have a standard way to present it. | |
| ### Printing API | |
| The error printing API should allow | |
| - consistent formatting and ordering, | |
| - detailed information that is only printed when requested (such as stack traces), | |
| - defining a chain of errors (possibly different from "reasons" or a programmatic chain), | |
| - localization of error messages, and | |
| - a formatting method that is easy for new error implementations to implement. | |
| The design presented here introduces two interfaces | |
| to satisfy these requirements: `Formatter` and `Printer`, | |
| both defined in the [`errors` package](https://golang.org/pkg/errors). | |
| An error that wants to provide additional detail implements the | |
| `errors.Formatter` interface’s `Format` method. | |
| The `Format` method is passed an `errors.Printer`, | |
| which itself has `Print` and `Printf` methods. | |
| package errors | |
| // A Formatter formats error messages. | |
| type Formatter interface { | |
| // Format is implemented by errors to print a single error message. | |
| // It should return the next error in the error chain, if any. | |
| Format(p Printer) (next error) | |
| } | |
| // A Printer creates formatted error messages. It enforces that | |
| // detailed information is written last. | |
| // | |
| // Printer is implemented by fmt. Localization packages may provide | |
| // their own implementation to support localized error messages | |
| // (see for instance golang.org/x/text/message). | |
| type Printer interface { | |
| // Print appends args to the message output. | |
| // String arguments are not localized, even within a localized context. | |
| Print(args ...interface{}) | |
| // Printf writes a formatted string. | |
| Printf(format string, args ...interface{}) | |
| // Detail reports whether error detail is requested. | |
| // After the first call to Detail, all text written to the Printer | |
| // is formatted as additional detail, or ignored when | |
| // detail has not been requested. | |
| // If Detail returns false, the caller can avoid printing the detail at all. | |
| Detail() bool | |
| } | |
| The `Printer` interface is designed to allow localization. | |
| The `Printer` implementation will typically be supplied by the | |
| [`fmt` package](https://golang.org/pkg/fmt) | |
| but can also be provided by localization frameworks such as | |
| [`golang.org/x/text/message`](https://golang.org/x/text/message). | |
| If instead a `Formatter` wrote to an `io.Writer`, | |
| localization with such packages would not be possible. | |
| In this example, `myAddrError` implements `Formatter`: | |
| Example: | |
| type myAddrError struct { | |
| address string | |
| detail string | |
| err error | |
| } | |
| func (e *myAddrError) Error() string { | |
| return fmt.Sprint(e) // delegate to Format | |
| } | |
| func (e *myAddrError) Format(p errors.Printer) error { | |
| p.Printf("address %s", e.address) | |
| if p.Detail() { | |
| p.Print(e.detail) | |
| } | |
| return e.err | |
| } | |
| This design assumes that the | |
| [`fmt` package](https://golang.org/pkg/fmt) | |
| and localization frameworks will add code to recognize | |
| errors that additionally implement `Formatter` | |
| and use that method for `%+v`. | |
| These packages already recognize `error`; recognizing `Formatter` is only a little more work. | |
| Advantages of this API: | |
| - This API clearly distinguishes informative detail from a causal error chain, giving less rise to confusion. | |
| - Consistency between different error implementations: | |
| - interpretation of formatting flags | |
| - ordering of the error chain | |
| - formatting and indentation | |
| - Less boilerplate for custom error types to implement: | |
| - only one interface to implement besides error | |
| - no need to implement `fmt.Formatter`. | |
| - Flexible: no assumption about the kind of detail information an error implementation might want to print. | |
| - Localizable: packages like golang.org/x/text/message can provide their own implementation of `errors.Printer` to allow translation of messages. | |
| - Detail information is more verbose and somewhat discouraged. | |
| - Performance: a single buffer can be used to print an error. | |
| - Users can implement `errors.Printer` to produce formats. | |
| ### Format | |
| Consider an error that returned by `foo` calling `bar` calling `baz`. An idiomatic Go error string would be: | |
| foo: bar(nameserver 139): baz flopped | |
| We suggest the following format for messages with diagnostics detail, | |
| assuming that each layer of wrapping adds additional diagnostics information. | |
| foo: | |
| file.go:123 main.main+0x123 | |
| --- bar(nameserver 139): | |
| some detail only text | |
| file.go:456 | |
| --- baz flopped: | |
| file.go:789 | |
| This output is somewhat akin to that of subtests. | |
| The first message is printed as formatted, | |
| but with the detail indented with 4 spaces. | |
| All subsequent messages are indented 4 spaces | |
| and prefixed with `---` and a space at the start of the message. | |
| Indenting the detail of the first message | |
| avoids ambiguity when multiple multiline errors | |
| are printed one after the other. | |
| ### Formatting verbs | |
| Today, `fmt.Printf` already prints errors using these verbs: | |
| - `%s`: `err.Error()` as a string | |
| - `%q`: `err.Error()` as a quoted string | |
| - `%+q`: `err.Error()` as an ASCII-only quoted string | |
| - `%v`: `err.Error()` as a string | |
| - `%#v`: `err` as a Go value, in Go syntax | |
| This design defines `%+v` to print the error in the detailed, multi-line format. | |
| ### Interaction with source line information | |
| The following API shows how printing stack traces, | |
| either top of the stack or full stacks per error, | |
| could interoperate with this package | |
| (only showing the parts of the API relevant to this discussion). | |
| package errstack | |
| type Stack struct { ... } | |
| // Format writes the stack information to p, but only | |
| // if detailed printing is requested. | |
| func (s *Stack) Format(p errors.Printer) { | |
| if p.Detail() { | |
| p.Printf(...) | |
| } | |
| } | |
| This package would be used by adding a `Stack` to each error implementation | |
| that wanted to record one: | |
| import ".../errstack" | |
| type myError struct { | |
| msg string | |
| stack errstack.Stack | |
| underlying error | |
| } | |
| func (e *myError) Format(p errors.Printer) error { | |
| p.Printf(e.msg) | |
| e.stack.Format(p) | |
| return e.underlying | |
| } | |
| func newError(msg string, underlying error) error { | |
| return &myError{ | |
| msg: msg, | |
| stack: errstack.New(), | |
| } | |
| } | |
| ### Localized errors | |
| The [`golang.org/x/text/message` package](https://golang.org/x/text/message) | |
| currently has its own | |
| implementation of `fmt`-style formatting. | |
| It would need to recognize `errors.Formatter` and | |
| provide its own implementation of `errors.Printer` | |
| with a translating `Printf` and localizing `Print`. | |
| import "golang.org/x/text/message" | |
| p := message.NewPrinter(language.Dutch) | |
| p.Printf("Error: %v", err) | |
| Any error passed to `%v` that implements `errors.Formatter` | |
| would use the localization machinery. | |
| Only format strings passed to `Printf` would be translated, | |
| although all values would be localized. | |
| Alternatively, since errors are always text, | |
| we could attempt to translate any error message, | |
| or at least to have `gotext` do static analysis | |
| similarly to what it does now for regular Go code. | |
| To facilitate localization, `golang.org/x/text/message` could implement | |
| an `Errorf` equivalent which delays the substitution of arguments | |
| until it is printed so that it can be properly localized. | |
| The `gotext` tool would have to be modified | |
| to extract error string formats from code. | |
| It should be easy to modify the analysis to pick up static error messages | |
| or error messages that are formatted using an `errors.Printer`'s `Printf` method. | |
| However, calls to `fmt.Errorf` will be problematic, | |
| as it substitutes the arguments prematurely. | |
| We may be able to change `fmt.Errorf` to evaluate and save its arguments | |
| but delay the final formatting. | |
| ### Error trees | |
| So far we have assumed that there is a single chain of errors. | |
| To implement formatting a tree of errors, an error list type | |
| could print itself as a new error chain, | |
| returning this single error with the entire chain as detail. | |
| Error list types occur fairly frequently, | |
| so it may be beneficial to standardize on an error list type to ensure consistency. | |
| The default output might look something like this: | |
| foo: bar: baz flopped (and 2 more errors) | |
| The detailed listing would show all the errors: | |
| foo: | |
| --- multiple errors: | |
| bar1 | |
| --- baz flopped | |
| bar2 | |
| bar3 | |
| ## Alternate designs | |
| We considered defining multiple optional methods, | |
| to provide fine-grained information such as the underlying error, detailed message, etc. | |
| This had many drawbacks: | |
| - Implementations needed to implement `fmt.Formatter` to correctly handle print verbs, | |
| which was cumbersome and led to inconsistencies and incompatibilities. | |
| - It required having two different methods returning the “next error” in the wrapping chain: | |
| one to report the next for error inspection and one to report the next for printing. | |
| It was difficult to remember which was which. | |
| - Error implementations needed too many methods. | |
| - Most such approaches were incompatible with localization. | |
| We also considered hiding the `Formatter` interface in the `fmt.State` implementation. | |
| This was clumsy to implement and it shared the drawback of requiring error implementation authors | |
| to understand how to implement all the relevant formatting verbs. | |
| ## Migration | |
| Packages that currently do their own formatting will have to be rewritten | |
| to use the new interfaces to maximize their utility. | |
| In experimental conversions of | |
| [`github.com/pkg/errors`](https://godoc.org/github.com/pkg/errors), | |
| [`gopkg.in/errgo.v2`](https://godoc.org/gopkg.in/errgo.v2), | |
| and | |
| [`upspin.io/errors`](https://upspin.io/errors), | |
| we found that implementing `Formatter` simplified printing logic considerably, | |
| with the simultaneous benefit of making chains of these errors | |
| interoperable. | |
| This design’s detailed, multiline form is always an expansion of the single-line form, | |
| proceeding through in the same order, outermost to innermost. | |
| Other packages, like [`github.com/pkg/errors`](https://godoc.org/github.com/pkg/errors), | |
| conventionally print detailed errors in the opposite order, contradicting the single-line form. | |
| Users used to reading those errors will need to learn to read the new format. | |
| ## Disadvantages | |
| The approach presented here does not provide any standard to programmatically | |
| extract the information that is to be displayed in the messages. | |
| It seems, though, there is no need for this. | |
| The goal of this approach is interoperability and standardization, not providing structured access. | |
| As noted in the previous section, existing error packages that | |
| print detail will need to update their formatting implementations, | |
| and some will find that the reporting order of errors has changed. | |
| This approach does not specify a standard for printing trees. | |
| Providing a standard error list type could help with this. |