cockroachdb / errors

Go error library with error portability over the network
Apache License 2.0
2.04k stars 66 forks source link

add formatting for multi-cause errors #115

Closed dhartunian closed 11 months ago

dhartunian commented 11 months ago

Previously, error formatting logic was based on a single linear chain of causality. Error causes would be printed vertically down the page, and their interpretation was natural.

This commit adds multi-cause formatting support with two goals in mind:

  1. Preserve output exactly as before for single-cause error chains
  2. Format multi-errors in a way that preserves the existing vertical layout style.

For non-verbose error display (.Error(), %s, %v) there are no changes implemented here. We rely on the error's own display logic and typically a multi-cause error will have a message within that has been constructed from all of its causes during instantiation.

For verbose error display (%+v) which relies on object introspection, whenever we encounter a multi-cause error in the chain, we mark that subtree as being displayed with markers for the "depth" of various causes. All child errors of the parent, will be at depth "1", their child errors will be at depth "2", etc. During display, we indent the error by its "depth" and add a └─ symbol to illustrate the parent/child relationship.

Example:

Printing the error produced by this construction using the format directive %+v

fmt.Errorf(
    "prefix1: %w",
    fmt.Errorf(
        "prefix2 %w",
        goErr.Join(
            fmt.Errorf("a%w", fmt.Errorf("b%w", fmt.Errorf("c%w", goErr.New("d")))),
            fmt.Errorf("e%w", fmt.Errorf("f%w", fmt.Errorf("g%w", goErr.New("h")))),
)))

Produces the following output:

prefix1: prefix2: abcd
(1) prefix1
Wraps: (2) prefix2
Wraps: (3) abcd
  | efgh
  └─ Wraps: (4) efgh
    └─ Wraps: (5) fgh
      └─ Wraps: (6) gh
        └─ Wraps: (7) h
  └─ Wraps: (8) abcd
    └─ Wraps: (9) bcd
      └─ Wraps: (10) cd
        └─ Wraps: (11) d
Error types: (1) *fmt.wrapError (2) *fmt.wrapError (3)
*errors.joinError (4) *fmt.wrapError (5) *fmt.wrapError (6)
*fmt.wrapError (7) *errors.errorString (8) *fmt.wrapError (9)
*fmt.wrapError (10) *fmt.wrapError (11) *errors.errorString`,

Note the following properties of the output:


Note for reviewers: only take a look at final commit


This change is Reviewable

dhartunian commented 11 months ago

@knz updated last PR and added a detailed example of changes. Tried to keep this pass simple and note in test comments the specific tweaks I want to fix later.

dhartunian commented 11 months ago

TFTR!