Skip to content

Figure out integration story for logging errors, especially using zap #6

@prashantv

Description

@prashantv

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions