Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to go 1.20 and add multi-cause error support #121

Merged
merged 13 commits into from
Aug 24, 2023

Conversation

dhartunian
Copy link
Contributor

@dhartunian dhartunian commented Aug 23, 2023

This work was done separately on the go-1.20-upgrade branch and reviewed in:

#113
#115
#118
#119
#120

This change is Reviewable

dhartunian and others added 12 commits August 16, 2023 19:19
Go 1.20 introduces the idea of an error with multiple causes instead
of a single chain. This commit updates the errors library to properly
encode, decode, and format these error types.

For encoding and decoding we use the existing `EncodedLeaf` type and
embellish it with a `causes` field. This is done in order to keep the
encoding/decoding backwards compatible. `EncodedLeaf` types containing
multiple causes when decided by earlier versions will simply see an
opaque leaf with a message inside. The reason the `EncodedWrapper`
is not used here is because the wrapper already contains a mandatory
single `cause` field that we cannot fill with the multi-errors. A
new type cannot be used because it would not be decodable by older
versions of this library.
support multi-cause errors in go 1.20
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:
* The top-level single cause chain maintains the left-aligned `Wrap`
lines which contain each cause one at a time.
* As soon as we hit the multi-cause errors within the `joinError`
struct (`(3)`), we add new indentation to show that The child errors
of `(3)` are `(4)` and `(8)`
* Subsequent causes of errors after `joinError`, are also indented to
disambiguate the causal chain
* The `Error types` section at the bottom remains the same and the
numbering of the types can be matched to the errors above using the
integers next to the `Wrap` lines.
* No special effort has been made to number the errors in a way that
describes the causal chain or tree. This keeps it easy to match up the
error to its type descriptor.
Previous support for multi-cause encode/decode functionality, did not
include support for custom encoder and decoder logic.

This commits adds the ability to register encoders and decoders for
multi-cause errors to encode custom types unknown to this library.
add formatting for multi-cause errors
This commit introduces a drop-in replacement of the go errors lib
`Join` method which constructs a simple multi-cause error object.

Some simple unit tests are added and `Join` wrappers are also
integrated into the datadriven formatting test. Our existing format
code for multi-cause errors is compatible with this new type which
allows us to remove the custom formatter from the implementation.

The public Join API contains wrappers that automatically attach
stacktraces to the join errors.

Signed-off-by: Steve Coffman <[email protected]>
Add Go 1.20 errors.Join support
This commit modifies the causal detail collection code in `report.go`
to recurse through both the single-cause chain and the multi-cause
chain if applicable when collecting information for the Sentry report.
Add multi-cause unwrap into Sentry reporting
update README with new public API changes
@@ -2,9 +2,9 @@ name: Go

on:
push:
branches: [ master ]
branches: [ master, go-1.20-upgrade ]
pull_request:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this can change here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@dhartunian dhartunian merged commit ca59e56 into master Aug 24, 2023
@dhartunian dhartunian deleted the go-1.20-upgrade branch August 24, 2023 20:08
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