We have an existing log macro in our code. Lets call it LOG_XXX where XXX is the log level to use, such as LOG_INFO, LOG_DEBUG, LOG_ERROR etc. We stream log messages to the macro:
LOG_INFO << "This is a log message.";
Under the covers, the macro creates a temporary object that exposes a stream. When the object is destroyed the logger is called with the stream contents.
As our codebase grows, we want to add structured logging with key value pairs. We would also like to add a mandatory "module name" to the macro, forcing callers to specify what logical module in the software is doing the logging.
LOG_INFO("some module) << make_pair("RequestId", "foo") << make_pair("Msg", "This is a log message.");
I can implement this by creating a different object whose constructor takes the module name, and overloads the << operator to take string pairs.
Now I can name this new macro LOG_INFO2 and be done with it. But that name feels ugly to me. What this really is conceptually is an overload, but macros can't be overloaded.
One route is to implement a selector that picks the right macro based on the number of arguments. But since the original call sites have no parantheses LOG_INFO(), I don't think this will work. I previously asked this question about it.
I can make callers directly instantiate the object but they shouldn't have to know what the object implementing all of this is:
LogWriter(module, LogLevel.Info) << make_pair("Msg", "This is a log message");
My overall goal is to not have to edit the original calls, and add the new functionality. Are there options other than the ones I've thought of? Is there something about the macro selection option that I am doing wrong?
You may change your macro to return an object with
operator ()(const std::string&)
, something like:Live Demo