Right now, if we do zap.Error(err)
and the error is a multierr
, then we end up with a single concatenated string that contains all the errors.
Ideally we would have output more similar to zap.Errors
when writing out a multiple errors.
The multierr
just returns an error
, which may be:
nil
if there were no errors
- single
error
if there was only one error
- type wrapping
[]error
if there's multiple errors
I think the experience we want is a single function that takes the error
, and returns either:
zap.Skip()
if there was no error
zap.Error(..)
if there's a single error
zap.Errors(...)
if there are multiple errors
cc @abhinav and @akshayjshah
I've documented some options for this below, I'm leaning towards the latter options. Would be great to document any other options.
Expose Causes(error) []error
from multierr
The user would be required to do:
var errs error
errs = multierr.Append(errs, ...)
logger.Error("Here are the errors.", zap.Errors(multierr.Causes(errs)))
The Causes
function would always allocate a slice, and return a slice with either 0, 1, or N elements depending on the input. The final output would always contain "errors" even if there's 0 errors or just 1 error.
Implement zap.MarshalLogArray
in the internal multierr
type
Since the underlying type is not exported, the user would probably do:
var errs error
errs = multierr.Append(errs, ...)
logger.Error("Here are the errors.", zap.Any("errors", errs))
This would require a dependency on zap
from multierr
-- if there is a dependency, I think the other way makes more sense.
The final output would always contain "errors" (similar to above).
Have zap implicitly recognize multierr
zap
could recognize a multierr error, and automatically get all the errors using Causes() []error
and use zap.Errors
instead.
zap
would need to depend on multierr
(which seems like a more reasonable dependency) but implicit handling may be a little unexpected.
Add explicit zap.MultiError(error)
field type
This would still require a dependency, but would make the checks more explicit. zap.MultiError
would return fields depending on the number of errors. This would be the ideal user experience, but extends the zap
interface to add another Field
constructor which takes error
. This may be confusing to users.
Have a subpackage of multierr
that provides the above field constructor
We could add a multierr/zerr
package for users logging multierr
with zap
. This avoids cross dependencies between the core packages, but the zerr
package could depend on both multierr
and zap
, and provide a field constructor Error(err error) zap.Field
that does the same logic as the previous option.
The user experience is a little worse since users now need to import a second package. However, the cost may be worth avoiding cross dependencies or extending zap's interface.