zerolog
zerolog copied to clipboard
Feature Request: Merge two zerolog Events together
Say I want to create a function for a struct that returns an Event - this doesn't get immediately logged but contains boilerplate of all the fields I want to log when it comes to that struct.
Example:
// Aggregation represents an aggregation task
type Aggregation struct {
TraceID string `json:"trace-id"`
AggregationID string `json:"aggregation-id"`
}
func (a Aggregation) LogEvent() *zerolog.Event {
event := zerolog.Event{}
return event.
Str("trace ID", a.TraceID).
Str("aggregation ID", a.AggregationID)
/// etc
}
I then want to consume this "event" where I will be actually logging. Something like:
log.Info().WithEvent(a.LogEvent).Str("Some Other Key", "Some Other Value").Msg("Some log message")
Apologies if this is already possible, I didn't see how in the documentation. I did see that I can embed a Dict() into Info(), but that required it's own key and implied that it gets pushed one level deeper rather than being a top level thing.
This would be a great feature for me if it isn't already possible. I've done something similar in Zap with some error handling, where we created our own error wrapper struct, and if you were passing the error up the chain you could annotate it with additional fields. When it was time to either handle or log the error, you could log it with all the fields that had been passed up the stack with the error - including a stack trace from when our code first saw the error. I find Zerolog's interface a bit easier to use and prefer it in my personal projects, and I'd love to be able to create a similar interface to wrap errors like that.
Can this not be achieved with sub-logging? For example
... event occurs ...
sublogger := log.With().
Str("trace ID", a.TraceID).
Str("aggregation ID", a.AggregationID)
Logger()
.. do some work ..
sublogger.Info().Msg("hello world")
// Output: {"level":"info","time":1494567715,"message":"hello world","trace Id":"foo", "aggregation Id": "bar"}
My particular use case isn't easily achieved with sub-logging, unfortunately. We are using Zap and wrote an error wrapper that takes Zap fields - this let us propagate an error up the stack, annotating it with data as it was passed upwards. When we got to the point where we either wanted to handle the error or log it, we would get all the Zap fields off the error that were added as it was passed up the stack, and then emit them in a single log message. This structure allowed us to keep our logs free of errors that we were handling, but have a full context and pseudo stack frames when we were logging an error that was generated a few layers below us in the call stack.
Our API looked something like this:
function doSomething(val int) error {
if err := doSomethingElse(val); err != nil {
// Annotate our error going up the stack with "int-val": val and "food": "pie"
return errors.ZapWrap(err, zap.Int("int-val", val), zap.Str("food", "pie"))
}
function handleOrLog() {
// Maybe we handle the error here and don't log it, but in this case, we do log it
if err := doSomething(42); err != nil {
// ZappedWrappedFields will convert the error back into the wrapped form and then return a slice of Zap fields that were annotated onto the error as it was passed back up the stack.
// If err wasn't a zap wrapped type, it will return just a basic zap.Error(err) so the error is included in the log entry.
// In this case, we will get a log entry that has msg: Got an error when doing something; int-val: 42; food: pie. If there were more errors.ZapWrap calls between the error and here, those values would be included as well
log.Error("Got an error when doing something", errors.ZapWrappedFields(err)...)
}
Makes sense. Maybe using Dict()
would work? you could do something like log.Dict("error_annotations", err.Dict())
or something similar if you implement a custom error type to do that work.
otherwise, ZapWrap could stuff a sublogger in the error itself, and then handleOrLog() grabs the logger out of the error (which by the union of all the fields) and do the logging.