Zerolog's API is designed to provide both a great developer experience and stunning performance. Its unique chaining API allows zerolog to write JSON (or CBOR) log events by avoiding allocations and reflection.
However it works fine when you log just in place. If you want to log somewhere above in stack you can lose moment to log context (struct in pretty format). I tried not log in place and produce json and put as part of error formatting. It can cause problem such as handle struct with nested json.RawMessage fields. Such problem can be easily overcome with zerolog Interface function
func doSmthAgain() error {
payload := Details{ID: "1", Message: []byte("message")}
json, err := structToJsonString(payload)
if err != nil {
return errors.Wrap(err, "structToJsonString")
}
// i want to log json
// I can log with zerolog right here
// logger.Err(err).Interface("json", json).Msg("doSmthAgain")
// if i wrap it is possible only use json in formatting so I lose power of zerolog
return errors.Wrap(fmt.Errorf("example %s", json), "doSmthAgain")
}
// first it does reflection
// then we compose map
// then we marshal it to json and cast to string
func structToJsonString(strct interface{}) (string, error) {
s := structs.New(strct)
m := make(map[string]string)
for _, f := range s.Fields() {
var v string
switch f.Value().(type) {
case json.RawMessage:
raw, _ := f.Value().(json.RawMessage) // it is already json.RawMessage, don't to check ok
v = string(raw)
case string:
str, _ := f.Value().(string)
v = string(str)
default:
// do smth
}
m[f.Name()] = v
}
json, err := json.Marshal(m)
if err != nil {
return "", errors.Wrap(err, "cannot marshal map")
}
return string(json), nil
}
If I log in place with zerolog and return error I log it actually twice because somewere above on stack I still have to handle error I return. So it ends with polluting logs.
func main() {
if err := doSmth(); err != nil {
// if log error before, here I log it second time
logger.Err(err).Msg("Oops!")
} else {
logger.Info().Msg("Phew! Everything is ok")
}
}
func doSmth() error {
if err := doSmthAgain(); err != nil {
return errors.Wrap(err, "doSmth")
}
return nil
}
func doSmthAgain() error {
payload := Details{ID: "1", Message: []byte("message")}
json, err := structToJsonString(payload)
if err != nil {
return errors.Wrap(err, "structToJsonString")
}
// assume I logged here with zerolog
logger.Err(err).Interface("json", json).Msg("doSmthAgain")
// here it is possible only use json in formatting
return errors.Wrap(fmt.Errorf("example %s", json), "doSmthAgain")
}
Are there some best practices? Probably I use zerolog in a wrong way?