syncman said:
In C, I would always make a log() function instead of using printfs,
so that I could disable the messages or otherwise process them, all in
one place. This worked in 1 line, for constant strings, but not for
variable values, as in:
char s[99];
sprintf (s, "The value is %d \n", value);
log (s); // 3 lines!!
In C++, is it really as bad as it was? If I use streams:
ostringstream oss;
oss << "The value is " << value << endl;
log (oss.str());
No better!
I suspect the answer might be to use clog:
clog << "The value is " << value << endl;
But I don't see any place where I can later customize or disable.
(I read that clog is the same as cerr, but clog is buffered.)
So, how do people log?
I've used a scheme like this one in the past. The actual logging class
was far more flexible than the one below but this example gives you an idea.
The interface is through the macro "LOG" and you pass it a logging level
and a title. The actual levels should be an enum but I just used an int
here for sake of simplicity (in a real system I would use mask).
LOG creates an object named "lout" which upon destruction will output
the log string.
i.e.
LOG( ERROR_LEVEL, "This is an error log" );
of
LOG( WARNING_LEVEL, "This is a warning" )
{
lout.attr( "oil_level" ) << oil_level << " is too low";
} // <<< message string is sent here
If logging is not enabled, no messages are created at all.
If you set the WARNING_LEVEL value to 0, the compiler will eliminate the
warning level logging code entirely.
THE one and only problem I've seen with this technique is that
occationally someone misses the ";" at the end and when logging is
turned on, the code works and when it is not it fails .... cute
I toyed with changing the interface to:
LOG( LOG_LEVEL, "Title" ) END_LOG
to eliminate this possibility of this kind of error but I'm still not
sure it's the best answer. Simplicity is better.
.... here is an example ....
#include <sstream>
#include <iostream>
#define LOG( LEVEL, TITLE ) \
if ( int x_level = (LEVEL) ) \
if ( Logger lout = x_level ) \
if ( lout.Title(TITLE) )
// end
struct LoggerContext
{
std:
stringstream m_oss;
int m_level;
mutable int m_count;
LoggerContext( int l_level )
: m_level( l_level ),
m_count( 1 )
{
}
};
struct Logger
{
LoggerContext * m_context;
static int min_level;
Logger( int l_level )
: m_context( l_level >= min_level ? new LoggerContext( l_level )
: 0 )
{
}
bool Title( const char * l_title )
{
m_context->m_oss << l_title << " : ";
return m_context;
}
template <typename T>
Logger & operator << ( const T & value )
{
m_context->m_oss << value;
return * this;
}
Logger & attr( const char * s_attr )
{
m_context->m_oss << " " << s_attr << " = ";
return * this;
}
~Logger()
{
if ( m_context )
{
-- m_context->m_count;
if ( m_context->m_count == 0 )
{
m_context->m_oss << "\n";
/// write log here
std::cout << m_context->m_oss.str();
delete m_context;
}
}
}
operator bool()
{
return m_context;
}
Logger( const Logger & l_rhs )
: m_context( l_rhs.getref() )
{
}
LoggerContext * getref() const
{
++ ( m_context->m_count );
return m_context;
}
};
int Logger::min_level = 2;
int main()
{
LOG( 3, "Thing 1" );
LOG( 4, "Thing 2" )
{
lout << "Really " << 33;
lout.attr( "Some value" ) << "54";
lout.attr( "Some other value" ) << 99.33;
}
LOG( 1, "Thing 3" )
{
lout << "Not really";
}
}