|
Boost : |
From: Fernando Cacciola (fcacciola_at_[hidden])
Date: 2002-04-09 10:02:27
I use logging very extensively in my developments.
Here is my view of the issue:
First, I like to keep log-specific functionality and format separated.
They are orthogonal IMO.
This means that I use a logger class which takes a straight "char const*" as
the message (even __FILE__, __LINE__, __FUNC__ must be manually bundled in
the msg from the pov of the logger).
A stream class if built upon the logger to aid easy formatting (with special
manipulators to control the logger specific stuff)
Second, I use a page-oriented logging (besides log levels).
Page-oriented logging is quite similar to that said by others in this
thread.
Essentially, all logs are directed to a page.
A page is described by a page descriptor and has a state which is ON or OFF.
Page states are external to the application, so they can be turned ON or OFF
while the app is running.
In my own systems, a page descriptor is built from two character strings
representing a Win32 registry (key,value) pair, and the page state is
dynamically obtained by reading the value from the registry.
I've found page-oriented logging fundamental, far more useful than simple
level control.
I use to debug an application using the Windows regedit.exe to turn on/off
the log pages so I can control which components I want to trace. Levels are
used only to control the amount of information obtained for the components
selected.
Third, I also use extensively simultaneous synchronized multiple outputs.
Typically, I use an external channel, which shows the log in a separate
application, and at the same time a file, so I can study the output after
the application finished in detail.
The above can be summarized with the following pseudo-code:
class page
{
public :
page () ; // Default Page, Always ON.
page ( char const* aWindowsRegistryKey, char const*
aWindowsRegistryValue ) ;
bool on() const ;
} ;
class channel
{
public :
channel() ;
void output ( char const* aText ) ;
private :
virtual void DoOutput ( char const* aText ) = 0 ;
} ;
// Example channels.
struct DebuggerChannel : public OutputChannel // Win32 OutputDebugString()
struct ConsoleChannel : public OutputChannel // std::cout
class ExternalChannel : public OutputChannel // Proprietary RPC-based
external listener.
class LogFileChannel : public OutputChannel // std::fstream
class logger
{
public :
static logger& instance() ;
channel_id add_channel ( boost::shared_ptr<channel> const& aChannel ) ;
void remove_channel ( channel_id aID ) ;
logger& send_text ( char const* aText ) ;
logger& set_page ( page const& aPage ) ;
logger& set_level ( int aLevel ) ;
} ;
I use macros in order to have debug-only logging.
(I don't know of a 100%-zero-overhead compile time solution for this)
A typical example of the logger usage would be:
#ifndef NDEBUG
#define LOG(pagek,pagev,level,msg)
logger::instance().set_page(page(pagek,pagev)).set_level(level).send_text(ms
g)
#define DEFLOG(level,msg)
logger::instance().set_page(page()).set_level(level).send_text(msg)
#else
#define LOG(pagek,pagev,level,msg)
#define DEFLOG(level,msg)
#endif
#define RELEAE_LOG(pagek,pagev,level,msg)
logger::instance().set_page(page(pagek,pagev)).set_level(level).send_text(ms
g)
#define MAKEC_STR(m)
((((std::ostringstream&)(((std::ostringstream&)std::ostringstream::ostringst
ream()) << m )).str()).c_str())
#define ABC_ERROR(msg) LOG("AB","C",error_level, MAKEC_STR(msg) )
#define ABC_TRACE(msg) LOG("AB","C",trace_level, MAKEC_STR(msg) )
int main()
{
#ifndef NDEBUG
boost::shared_ptr<channel> ExtChannel( new ExternalChannel ) ;
boost::shared_ptr<channel> FileChannel( new
LogFileChannel("c:\\my_log.txt") ) ;
logger::instance().add_chanel ( ExtChannel ) ;
logger::instance().add_chanel ( FileChannel ) ;
#endif
try
{
int n = foo() ;
if ( n > 0 )
{
ABC_TRACE ( "n=" << n << '\n' ) ;
...
}
else
{
ABC_ERROR( "Something's wrong here. n is zero\n' );
}
}
catch ( std::exception const& x)
{
RELEASE_LOG("AppError","CppException", x.what() << endl ) :
}
}
In the next, level, we can get rid of the ugly and error prone MAKEC_STR()
by building a logger stream on top of logger.
So the LOG macros would look like instead:
#define LOG(pagek,pagev,level,msg) lout << set_page(page(pagek,pagev)) <<
set_level(level) << msg
Even one level further, I very often use pages directly in this way:
#ifndef NDEBUG
#define IF_PAGE_IS_ON(pagek,pagev,code) if ( page(pagek,pagev) ) { code }
#else
#define IF_PAGE_IS_ON(pagek,pagev,code)
#endif
#define IF_EDGE_DETECTOR_SHOW_INPUT(code)
IF_PAGE_IS_ON("EdgeDetector","ShowInput",code)
int main()
{
IF_EDGE_DETECTOR_SHOW_INPUT
(
image.write_to_file ( "c:\\input_image.bmp" ) ;
)
}
Just my two cents..
Fernando Cacciola
Sierra s.r.l.
fcacciola_at_[hidden]
www.gosierra.com
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk