Based on some suggestions in Log Module Suggestions and some of my own thoughts on logging
TL;DR It would be helpful if all data in Log::Entry was key/value data
- Key/value data is often more helpful for both humans and machines
- It is a common pattern in most metrics system
- Many loggers also have this format
- Log consumers often provide a better search experience when data is key/value (typically formatted as JSON)
The proposal
I think we should have Log::Entry#context
and Log::Entry#message_data
.
I can already hear some criticisms āsometimes I just want to log a String!ā or āWhat about backwards compatability!ā
I think these can be resolved quite elegantly and also allow for much better flexibility with how things are logged
Letās dive into an example API
It looks the same!
Log.info { "Oh hai!" }
So for simple messages you can still pass a simple string like today.
Ok, but whatās the new stuff?
Log.info { {query: sql_query, args: args} }
This letās you provide data in a more human readable and machine readable format.
This is great because:
- The simple case of logging a String is the same
- Add the option to log key/value data
Under the hood
Under the hood a raw string message is assigned to Log::Entry#message_data
as {message: "the string"}
. So it is basically made into key/value data
Formatters
Now formatters can always rely on the data being in a key/value format. This is fantastic for JSON formatters or formatters like Luckyās PrettyLogFormatter that does a lot of stuff to make logs human readable.
The default io formatter would basically change from entry.message
to entry.message_data.to_s
The advantage?
Context is separate from the actual log data and allows better formatter
Right now the next version of Dexter basically makes this work using Log::Context
to store the entry data dexter/src/dexter/log.cr at af4feded4d31698e2c6d8e602516d54925540706 Ā· luckyframework/dexter Ā· GitHub
The big problem with this approach is that context is mushed together with the log entry. Ideally Iād prefer to print the message data first and then the context but that is not possible since I canāt tell which is which. There are also cases where I may want to ignore context data in a pretty log formatter, this is also not possible right now
EDIT: I canāt just reverse the context data because some of the data may be in a particular order. Like {failed_operation: "MyOperation", failure_message: big_array_of_messages_that_we_dont_want_first}
Backwards compatability
We can add a deprecated Log::Entry#message
that basically calls message_data.to_s
so it works with any custom formatters out there that rely on it (probably not a lot since Log
is brand spanking new)
Can still print just the message data if you want
Your formatter could print just the message string if you really want to:
if entry.message_data.as_h.keys == "message"
io << entry.message_data["message"].as_s
else
io << entry.message_data.to_s
end
Next steps
I would be happy to open up a draft PR as that may make it easier to understand what I am saying, but I think there is nothing to lose, and plenty to gain.
- You can still log just a string message
- You can still have your formatter log just the string if you want
- And also allows more flexibility for formatters to use the data (this is the big deal thing)
Seems like a win/win to me