Error wrapper that allows adding zap fields to an error, and fetching them at a later stage
To install, run:
go get github.com/yzzyx/zerr
To wrap an error with additional field:
// Add int field to error
err = zerr.Wrap(err).WithInt("int-field", 15)
// or, using zap directly
err = zerr.Wrap(err, zap.Int("int-field", 15))
You can add any number of fields the the error:
// Add multiple fields to error
err = zerr.Wrap(err).WithInt("int-field", 15).WithString("query", query).WithAny("obj", obj)
// or, using zap directly
err = zerr.Wrap(err, zap.Int("int-field", 15), zap.String("query", query), zap.Any("obj", obj))
By default, a stacktrace is added when an error is wrapped.
To avoid this behaviour call zerr.WrapNoStack()
instead. This allows for a specific error to be wrapped without a stacktrace, regardless of the
setting DefaultAddStacktrace.
// Do not include a stacktrace
err = zerr.WrapNoStack(err)
// WrapNoStack can also take extra fields, just like Wrap()
err = zerr.WrapNoStack(err, zap.Int("int-field", 15), zap.String("query", query))
// or be chained
err = zerr.WrapNoStack(err).WithInt("int-field", 15).WithString("query", query)
Note that Wrap
will not add additional stacktraces if one was already included in the error.
If additional stacktraces should be included, it must be specified explicitly, by calling zerr.Wrap
with a field created with zap.Stack()
Errors can be wrapped multiple times. All added fields, regardless of level, will be extracted.
For ease of use, sugared versions are availble of the Wrap()-functions, which expects an error followed by a list of alternating string keynames and values to be passed as arguments.
err := zerr.Sugar(err, "fieldname1", intvalue1, "fieldname2", stringvalue2)
// which is equal to:
err = zerr.Wrap(err).WithInt("fieldname1", intvalue1).WithString("fieldname2", stringvalue2)
A corresponding function zerr.SugarNoStack
is available to wrap an error without a stack trace
The zerr package contains a wrapper Field for conveniently logging HTTP requests.
zerr.Wrap(err).WithRequest("request", r))
It is possible to create a new error with additional fields with the methods WithInt()
, WithString()
, WithAny()
etc.
Using the WithField()
method, a zap Field can be wrapped directly.
ze1 := zerr.Wrap(err)
ze2 := ze.WithField(zap.Int("test", 1))
Using zerr allows capturing errors with additional context information deep down in the call stack, and the returning this error back up to a level were logging can take place, while still having access to the context and stacktrace to where the error actually occurred.
func broken(fname string) error {
_, err := os.Open(fname)
if err != nil {
return zerr.Wrap(err, zap.String("filename", fname))
}
// do something else
return nil
}
func main() {
logger := zap.NewDevelopment()
err = broken("/this-file-does-not-exist")
if err != nil {
// Log error to logger, using the intial error as message
zerr.Wrap(err).LogError(logger)
// Or, if we want to add additional fields:
zerr.Wrap(err).WithInt("extra", 15)).LogError(logger)
// Logging with different level
zerr.Wrap(err).WithInt("extra", 15)).LogInfo(logger)
// Adding HTTP request info
zerr.Wrap(err).WithRequest(r).LogError(logger)
// Or, to call logger with a specific message:
logger.Error("error calling broken", zerr.Fields(err)...)
}
}
In order to extract the field data from errors, use the function zerr.Fields
.
// Extract any additional fields from error and log
if err != nil {
zap.Error("something went wrong", zerr.Fields(err)...)
}