I am trying to do a simple logging library only for my own. I know there exists several once, but I have not found any header-only, small and very “c++” like logging library which fits into my application.
Currently I have the following syntax:
logger << debug << "A debug message" << end; //debug and end is my custom manipulators
I have implemented all necessary operator<< and it works great, specially when it have backward compatibility with std::ostream. But I wonder, just for efficiency if it is a why to stop evaluate anything if some message should not be logged (after debug in the example)? Making everything after the severity manipulator “disappear”?
Just now do I have the following code in short:
template <typename Type>
Logger & Logger::operator<<(const Type & message)
{
if(this->get_message_level() <= this->get_severity())
{
BOOST_FOREACH(std::ostream* stream, this->_sinks)
{
*stream << message;
}
}
return *this;
}
Logger & Logger::operator<< (Logger & (*pf)(Logger&))
{
return pf(*this);
}
Logger & debug(Logger& logger)
{
logger.lock();
logger.set_severity(7);
//...
return logger;
}
Logger & end(Logger& logger)
{
logger << std::endl;
logger.unlock();
return logger;
}
Thanks in advance.
It can be a bit tricky, depending on what compromizes you’re willing to
accept in the syntax. If you really want to support everything that
outputting to an
ostreamdoes, then the best you can do (as far as Iknow) is a wrapper class, along the lines of:
If logging is turned off (
Logger::myDest == NULL), none of theconversion code will execute, but you’ll still evaluate each of the
arguments. In my experience, this is not usually an issue, since most
of the arguments are either string literals or a simple variable, but
it’s not a total 0 cost. It also has the potential disadvantage that
the propagated type is not
std::ostream&(although I’ve never foundthis to be a problem in practice).
A somewhat tricker solution would be to use a macro along the lines of:
This will still allow most of the actual uses with the same syntax
(because the
&&operator has very low precedence), but could fail incases where someone has tried to be too clever, and embedded the logging
output in a more complicated expression. In addition to not doing the
conversions, the arguments are not even evaluated if logging is turned
off.
Finally, if you accept a different syntax, you can write something like:
This requires the user to write
LOG("x = " << x), instead oflog <<, and requires recompiling if you want to turn logging on,"x = " << x
but it is the only solution I know which has absolute 0 cost if logging
is turned off.
In my experience, most applications can support the first solution; in a
very few cases, you might want to use the second; and I’ve never seen an
application where performance required the third.
Note that even with the first, you’ll probably want to use a macro to
get the logger instance, in order to automatically insert
__FILE__and__LINE__, and that in the second, you’ll probably still want to use awrapper class, in order to ensure a flush in the destructor; if the
application is multithreaded, you’ll want the wrapper in all cases, in
order to make the entire sequence of output atomic.