Our project uses a macro to make logging easy and simple in one-line statements, like so:
DEBUG_LOG(TRACE_LOG_LEVEL, "The X value = " << x << ", pointer = " << *x);
The macro translates the 2nd parameter into stringstream arguments, and sends it off to a regular C++ logger. This works great in practice, as it makes multi-parameter logging statements very concise. However, Scott Meyers has said, in Effective C++ 3rd Edition, “You can get all the efficiency of a macro plus all the predictable behavior and type safety of a regular function by using a template for an inline function” (Item 2). I know there are many issues with macro usage in C++ related to predictable behavior, so I’m trying to eliminate as many macros as possible in our code base.
My logging macro is defined similar to:
#define DEBUG_LOG(aLogLevel, aWhat) { \
if (isEnabled(aLogLevel)) { \
std::stringstream outStr; \
outStr<< __FILE__ << "(" << __LINE__ << ") [" << getpid() << "] : " << aWhat; \
logger::log(aLogLevel, outStr.str()); \
}
I’ve tried several times to rewrite this into something that doesn’t use macros, including:
inline void DEBUG_LOG(LogLevel aLogLevel, const std::stringstream& aWhat) {
...
}
And…
template<typename WhatT> inline void DEBUG_LOG(LogLevel aLogLevel, WhatT aWhat) {
... }
To no avail (neither of the above 2 rewrites will compile against our logging code in the 1st example). Any other ideas? Can this be done? Or is it best to just leave it as a macro?
Logging remains one of the few places were you can’t completely do away with macros, as you need call-site information (
__LINE__,__FILE__, …) that isn’t available otherwise. See also this question.You can, however, move the logging logic into a seperate function (or object) and provide just the call-site information through a macro. You don’t even need a template function for this.
With this, the usage remains the same, which might be a good idea so you don’t have to change a load of code. With the
&&, you get the same short-curcuit behaviour as you do with yourifclause.Now, the
scoped_loggerwill be a RAII object that will actually log what it gets when it’s destroyed, aka in the destructor.Exposing the underlying
std::stringstreamobject saves us the trouble of having to write our ownoperator<<overloads (which would be silly). The need to actually expose it through a function is important; if thescoped_loggerobject is a temporary (an rvalue), so is thestd::stringstreammember and only member overloads ofoperator<<will be found if we don’t somehow transform it to an lvalue (reference). You can read more about this problem here (note that this problem has been fixed in C++11 with rvalue stream inserters). This “transformation” is done by calling a member function that simply returns a normal reference to the stream.Small live example on Ideone.