Boost logo

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