Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

logging made easy in your c++ applications

2.72/5 (15 votes)
17 Oct 2006CPOL6 min read 1   2.3K  
This component is used for logging purpose

Introduction

I would like to discuss about Log4Cplus is an opensource component for logging purspose used in C++ applications. Logging is an essential component of development cycle of large scale applications The main advantage of logging API over plain printf resides in its ability to disable certain log statements while allowing others to print unhindered. This capability assumes that the logging space, that is, the space of all possible logging statements, is categorized according to some developer-chosen criteria. It provides precise context about a run of the application. Once inserted into the code, the generation of logging output requires no human intervention. Moreover, log output can be saved in a persistent medium to be studied at a later time. In addition to its use in the development cycle, a sufficiently-rich logging package can also be viewed as an auditing tool. Logging does have its drawbacks. It can slow down an application. If too verbose, it can cause scrolling blindness. To alleviate these concerns, log4cplus is designed to be reliable, fast and extensible.

First let me discuss few key concepts of the API. Log4cplus has three main components:

  • Layouts
  • Appenders
  • Loggers

These three types of components work together to enable developers to log messages according to message type and level, and to control at runtime how these messages are formatted and where they are reported.

A layout class determines the format of a log message. You may derive your own classes from Layout, to specify any style of output message that you want. log4cplus comes with three layout classes, SimpleLayout, TTCCLayout and PatternLayout. The following are the examples

SimpleLayout : DEBUG - Hello world

TTCCLayout : 225 [main] INFO Hello World

PatternLayout:INFO 21 May 2001 11:00:57,109 HELLO WORLD

An appender class writes the trace message out to some device. log4cplus comes with classes to "append" to standard out, or a named file. The Appender class works closely with the Layout class, and once again you may derive your own appender classes if you wish to log to a socket, a shared memory buffer, or some sort of delayed write device. Maybe most useful appenders built in log4cplus are: FileAppender, RollingFileAppender, and ConsoleAppender. Some of the basic methods in the appender class are listed below.

Appender : public log4cplus::helpers::SharedObject
{

// Set the layout for this appender

void setLayout(std::auto_ptr<Layout> layout)

// Set the filter chain on this Appender.

void setFilter(log4cplus::spi::FilterPtr f) 

// Set the threshold LogLevel

void setThreshold(LogLevel th) 

//This method performs threshold checks and invokes filters before delegating actual logging to the subclasses specific

void doAppend(const log4cplus::spi::InternalLoggingEvent& event)

}

A logger class does the actual logging. A logger object has two main parts, appenders and log level. Log level controls which messages can be logged by a particular class. When a logger object is created, it starts life with a default appender of standard out and a default priority of none. One or more appenders can be added to the list of destinations for logging. The priority of a logger can be set to NOT_SET_LOG_LEVEL, TRACE_LOG_LEVEL, DEBUG_LOG_LEVEL, INFO_LOG_LEVEL, WARN_LOG_LEVEL, ERROR_LOG_LEVEL, FATAL_LOG_LEVEL in ascending order of "logginess" or importance level of each message. FATAL and EMERG are two names for the same highest level of importance. Each message is logged to a logger object. The logger object has a priority level. The message itself also has a priority level as it wends its way to the log. If the priority of the message is greater than, or equal to, the priority of the logger, then logging takes place, otherwise the message is ignored. The logger priorities are arranged such that : FATAL_LOG_LEVEL > ERROR_LOG_LEVEL > WARN_LOG_LEVEL > INFO_LOG_LEVEL > DEBUG_LOG_LEVEL > TRACE_LOG_LEVEL > NOT_SET_LOG_LEVEL. NOT_SET_LOG_LEVEL is the lowest and if a logger object is left with a NOTSET priority, it will accept and log any message.

Appenders accumulate as a collection on a hierarchy object. When an appender is added to a logger, the default behavior is for the appender to be added to the list of destinations for logging messages. It would be possible to log/trace into two files simultaneously as well as echoing to standard out. But it's possible to set the additivity flag to false, if you do that, adding an appender to a logger will replace the old appender by the new one. By default, loggers do not have a set LogLevel but inherit it from the hierarchy. This is one of the central features of log4cplus. Otherwise, a new instance is created. Some of the basic methods in the Logger class are listed below.

class Logger : public log4cplus::spi::AppenderAttachable
{

static bool exists(const log4cplus::tstring& name)

//Returns all the currently defined loggers in the default hierarchy.

static LoggerList getCurrentLoggers()

// Return the default Hierarchy instance.

static Hierarchy& getDefaultHierarchy()

static Logger getInstance(const log4cplus::tstring& name)

static Logger getRoot()

// Calling this method will close and remove all appenders in all the loggers 
including root contained in the default hierachy. Some appenders such as 
SocketAppender need to be closed before the application exits. Otherwise, 
pending logging events might be lost.

static void shutdown()

// This generic form is intended to be used by wrappers.
void log(LogLevel ll, const log4cplus::tstring& message, const char* 
file=NULL, int line=-1)

// This method creates a new logging event and logs the 
event without further checks.
void forcedLog(LogLevel ll, const log4cplus::tstring& message, const 
char* file=NULL, int line=-1)

// This method calls all the appenders inherited from the hierarchy 
circumventing any evaluation of whether to log or not to log the particular log 
request.

void callAppenders(const spi::InternalLoggingEvent& event)

// returns LogLevel - the assigned LogLevel 

void setLogLevel(LogLevel)

// Return the logger name 

log4cplus::tstring getName() const

virtual void addAppender(SharedAppenderPtr newAppender)

virtual SharedAppenderPtrList getAllAppenders()
<CODE>virtual SharedAppenderPtr getAppender(const log4cplus::tstring& name)

virtual void removeAllAppenders()

virtual void removeAppender(SharedAppenderPtr appender)

virtual void removeAppender(const log4cplus::tstring& name)

}

Application framework provides access to a Logger object from every class of the framework. This logger object should be used with the Table 1 macros to create the log events

Severity

Log4Cplus macro

Debug

LOG4CPLUS_DEBUG

Low

LOG4CPLUS_INFO

LOG4CPLUS_WARN

LOG4CPLUS_ERROR

High

LOG4CPLUS_FATAL

Table 1

Depending on the class where the logging event is sent a different method should be called in order to get the reference of the Logger object. In typical situation

Log4Cplus::Logger& log = getApplicationLogger()

LOG4CPLUS_INFO(log, "here put some information")

NDC

A Nested Diagnostic Context , or NDC in short, is an instrument to distinguish interleaved log output from different sources. Log output is typically interleaved when a server handles multiple clients near-simultaneously. Interleaved log output can still be meaningful if each log entry from different contexts had a distinctive stamp. This is the role of the NDCs.

To build an NDC call the push() method:

  • Contexts can be nested.
  • When entering a context, call NDC.push. As a side effect, if there is no nested diagnostic context for the current thread, this method will create it.
  • When leaving a context, call NDC.pop.
  • When exiting a thread make sure to call NDC.remove().

Log4Cplus is available for all major platforms. It can be downloaded from http://sourceforge.net/projects/log4cplus/

Basic Usage

To use log4cplus in your source:

1. First, you have to include files

<pre>  #include "log4cplus/logger.h"
        #include "log4cplus/loglevel.h"
        #include "log4cplus/layout.h"
        #include "log4cplus/ndc.h"  </pre>

a. If you want a console logger, you have to include these file too:

b.      <pre>   include "log4cplus/consoleappender.h"  </pre>         

c. If you want a file logger, you have to include these file too:

d.      <pre>    #include "log4cplus/fileappender.h"   </pre>       

2. For a easiest way of use you could write:

<pre>using namespace log4cplus; </pre>

3. Instantiate an appender object. Depending of which logger you want, you have to:

a. Instantiate a console appender:

b.  <pre>SharedAppenderPtr myAppender(new ConsoleAppender());</pre>
c.  <pre>myAppender->setName("myAppenderName");</pre>           

d. Instantiate a file appender:

e.  <pre>SharedAppenderPtr myAppender(new FileAppender("myLogFile.log"));</pre>
f.  <pre>myAppender->setName("myAppenderName");</pre>           

Replace "myLogFile.log" with the name of log file you like.

4. Instantiate a layout object:

<pre>std::auto_ptr<Layout>; myLayout = std::auto_ptr<Layout>(new log4cplus::TTCCLayout());</pre>

5. Attach the layout object to the appender object:

<pre>myAppender->setLayout( myLayout );</pre>

6. Instantiate the logger attach an appender on it:

<pre>Logger myLogger= Logger::getInstance("myLoggerName");</pre>

7. Attach the appender to the logger:

<pre>myLogger.addAppender(myAppender);</pre>

8. Set the loglevel of the logger to info for example:

<pre>myLogger.setLogLevel ( INFO_LOG_LEVEL );</pre>

The others loglevel are : FATAL_LOG_LEVEL > ERROR_LOG_LEVEL > WARN_LOG_LEVEL > INFO_LOG_LEVEL > DEBUG_LOG_LEVEL > TRACE_LOG_LEVEL > NOT_SET_LOG_LEVEL.

9. When you want to log a message, use one of these macro depending of the log level the message to log:

<pre>LOG4CPLUS_FATAL(myLogger, "logEvent");//for a fatal priority event</pre>
<pre>LOG4CPLUS_ERROR(myLogger, "logEvent");//for a error priority event</pre>
<pre>LOG4CPLUS_WARN(myLogger, "logEvent") ;//for a warn priority event</pre>
<pre>LOG4CPLUS_INFO(myLogger, "logEvent");      //for a info priority event</pre>
<pre>LOG4CPLUS_DEBUG(myLogger, "logEvent");//for a debug priority event</pre>

10. By defining a LOG4CPLUS_DISABLE_*, the macros will be disable and won't even be compiled into your executable. * could be : DEBUG, INFO, or WARN. For example, adding:

<pre>#define LOG4CPLUS_DISABLE_INFO
#define LOG4CPLUS_DISABLE_WARN
#define LOG4CPLUS_DISABLE_DEBUG</pre>

at the begining of your program will disable the DEBUG, INFO, and WARN loglevel.

Conclusions

One of the advantages of the log4cplus is its manageability. Once the log statements have been inserted into the code, they can be controlled with configuration files. They can be selectively enabled or disabled, and sent to different and multiple output targets in user-chosen formats. The log4cplus package is designed so that log statements can remain in shipped code without incurring a heavy performance cost.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)