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

errors: Support multierr and pkg/errors' causer #460

Merged
merged 3 commits into from
Jun 30, 2017
Merged

Conversation

abhinav
Copy link
Collaborator

@abhinav abhinav commented Jun 30, 2017

This changes how we log errors to break down multierr and pkg/errors
errors if possible.

If an error is a multierr error, its individual items will be listed
under ${key}Causes as objects. Roughly,

"error": "foo; bar; baz",
"errorVerbose":
    `the following errors occurred:
      -  foo
      -  bar
      -  baz`,
"errorCauses": [
    {"error": "foo"},
    {"error": "bar"},
    {"error": "baz"},
]

Similarly, if an error is a pkg/errors causer, the cause will be listed
under ${key}Causes by itself.

"error": "foo: bar",
"errorVerbose": "foo: bar\n[stack trace]"
"errorCauses": [
    {
        "error": "bar",
        "errorVerbose": "bar\n[stack trace]",
    },
]

See: uber-go/multierr#6

@codecov
Copy link

codecov bot commented Jun 30, 2017

Codecov Report

Merging #460 into master will increase coverage by 0.04%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #460      +/-   ##
==========================================
+ Coverage   97.17%   97.22%   +0.04%     
==========================================
  Files          35       36       +1     
  Lines        1876     1907      +31     
==========================================
+ Hits         1823     1854      +31     
  Misses         43       43              
  Partials       10       10
Impacted Files Coverage Δ
error.go 100% <ø> (ø) ⬆️
zapcore/field.go 97.4% <100%> (-0.19%) ⬇️
zapcore/error.go 100% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0bd02a6...506766d. Read the comment docs.

Copy link
Contributor

@akshayjshah akshayjshah left a comment

Choose a reason for hiding this comment

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

Apart from name nit, lgtm.

zapcore/error.go Outdated
return encodeError("error", e.err, enc)
}

func (e *errArrayElem) Release() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Elsewhere in zap, I believe that we're calling these methods Free. Would be nice to be consistent here.

This changes how we log errors to break down multierr and pkg/errors
errors if possible.

If an error is a multierr error, its individual items will be listed
under `${key}Causes` as objects. Roughly,

    "error": "foo; bar; baz",
    "errorVerbose":
        `the following errors occurred:
          -  foo
          -  bar
          -  baz`,
    "errorCauses": [
        {"error": "foo"},
        {"error": "bar"},
        {"error": "baz"},
    ]

Similarly, if an error is a pkg/errors causer, the cause will be listed
under `${key}Causes` by itself.

    "error": "foo: bar",
    "errorVerbose": "foo: bar\n[stack trace]"
    "errorCauses": [
        {
            "error": "bar",
            "errorVerbose": "bar\n[stack trace]",
        },
    ]

See: uber-go/multierr#6
@abhinav abhinav merged commit acea907 into master Jun 30, 2017
@abhinav abhinav deleted the multierr-integration branch June 30, 2017 21:50
Copy link
Collaborator

@prashantv prashantv left a comment

Choose a reason for hiding this comment

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

I don't think we should release this integration -- this is a pretty poor user experience. If they use a multierr, they now get 3 copies of the same error, one separated by semicolons, one by newlines, and one as separate array elements.

For Causer, it makes sense to have errorCauses and the %+v version, since we get the list of errors separately from one, and the stack trace from the other.

@akshayjshah
Copy link
Contributor

akshayjshah commented Jul 1, 2017

@prashantv That's a good point, and one that I didn't stop to consider fully before we landed this. Appreciate the vigilance.

I mulled this over a while last night, and I'm increasingly dissatisfied with this whole approach to fancy errors - it feels odd and un-idiomatic to me. The error interface is just Error() string, and I'd really like to respect that and log all errors as a single string. Would we be globally happier if we took a simpler approach?

  1. For most errors, log err.Error().
  2. For errors that implement fmt.Formatter, log fmt.Sprintf("+v", err). Users of pkg/errors, for example, usually want this verbose output, and they can always get terse output by explicitly logging err.Error().

If we don't like multierr's multi-line representation in logs, perhaps that's a sign that we should reconsider that decision. Maybe 3 errors (foo; bar; baz) or just foo; bar; baz would be more generally useful?

@prashantv
Copy link
Collaborator

I'm not convinced that we should do something special for how pkg/errors works, but not for multierr. The main issue is that the interfaces we're dealing with don't really specify how they're used. E.g., even if a type implements Formatter, there's no guarantee it'll have unique output.

The simplest option is to have zap.Error output error: err.Error(). It's unsurprising and doesn't automatically add any extra fields, nor does it bloat the output if a pkg/errors error is used. The issue with forcing users to call err.Error() is:

  • The callsite changes from a simple zap.Error(err) to zap.String("error", err.Error())
  • If the err was somehow nil, it'll panic

Perhaps we should have a separate ErrorVerbose that does extra checks, but in that I'd want to detect multierr explicitly, and only log error and errorCauses for it.

@ansel1
Copy link
Contributor

ansel1 commented Jul 4, 2017 via email

@akshayjshah
Copy link
Contributor

Let's discuss on uber-go/multierr#23 - I made the mistake of kicking this discussion off in two places, and it's a bit confusing.

akshayjshah pushed a commit that referenced this pull request Jul 6, 2017
Following the discussions on #460, uber-go/multierr#6, and
(most recently) uber-go/multierr#23, reduce log verbosity for
`multierr`. This is fully backward-compatible with the last released
version of zap.

The small changes introduced here do two things:

1. First, we either report `errorCauses` or `errorVerbose`, but not
   both.
2. Second, we prefer `errorCauses` to `errorVerbose`.

I think that this addresses our top-level wants without breaking any
interfaces or removing behavior we've already shipped.

If we ever decide to cut a new major release of zap, we should treat
errors like durations and times - they're special types for which users
choose a formatter.

In a future release, we can add an `ErrorEncoder` interface that the
JSON encoder and console encoder implement, and make the error field
attempt an upcast into that type. That would let the user supply their
own error encoder (much like they supply their own time and duration
encoders now). Even if we do that, though, I suspect that we'll want to
preserve the behavior here as the default.
akshayjshah added a commit that referenced this pull request Jul 7, 2017
Following the discussions on #460, uber-go/multierr#6, and
(most recently) uber-go/multierr#23, reduce log verbosity for
`multierr`. This is fully backward-compatible with the last released
version of zap.

The small changes introduced here do two things:

1. First, we either report `errorCauses` or `errorVerbose`, but not
   both.
2. Second, we prefer `errorCauses` to `errorVerbose`.

I think that this addresses our top-level wants without breaking any
interfaces or removing behavior we've already shipped.

If we ever decide to cut a new major release of zap, we should treat
errors like durations and times - they're special types for which users
choose a formatter.

In a future release, we can add an `ErrorEncoder` interface that the
JSON encoder and console encoder implement, and make the error field
attempt an upcast into that type. That would let the user supply their
own error encoder (much like they supply their own time and duration
encoders now). Even if we do that, though, I suspect that we'll want to
preserve the behavior here as the default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants