Last Updated: November 25, 2021
·
21.07K
· themichael'tips

Log4cplus Essential

Introduction

Log4cplus is derived by the popular Log4j written in java.<br>
This tutorial show how create:

  • a configuration file
    • define custom log for classes
    • define custom appenders
  • code example
    • how and when use the different log levels

Log levels

To deal with Log4cplus you must understand the concept of log levels which are:

ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF

Understand with examples:

  • If a class is defined over a log level ERROR, all the messages that the class spawned, are printed only if the message is tagged with the log level equals to ERROR or FATAL.
  • If a class is defined over a log level DEBUG, all the messages that the class spawned, are printed only if the message is tagged with the log leves equals to DEBUG or INFO or WARN or ERROR or FATAL
  • Spawned a message with DEBUG level:
    • LOG4CPLUSDEBUG(logger, "x = " << x);

ALL and OFF levels can't be used as the previous code, they are just flags that switch all other kind of levels respectively to on or off.

Configuration file

The concepts for create a configuration file are:

  • Log4cplus is able to print on file and console.
  • File and console are called appenders: they can be considered as the cpp stream concept.
  • A class can be associated with several appenders.
  • All the classes inherit from an abstract root class the log level and the appenders.
  • Log4cplus give the ability to print the thread-id of the worker that spawned the message: very useful to inspect threads.

Below is an extract of my working config file:

log4cplus_configure.ini

###################################
########Define log Levels##########
###################################

#All classes - except those in log4cplus.logger.* - use DEBUG level to print information on file
log4cplus.rootLogger=DEBUG, MyFileAppender

#For MemoryCheck class I need to inspect all the details, and I want print information even to the console
log4cplus.logger.MemoryCheck=TRACE, MyConsoleAppender

#For database stuff, I don't need to logging everything, it's enough printing only errors!
log4cplus.logger.DatabaseOperations=ERROR

###################################
########Define the Appenders#######
###################################

#MyConsoleAppender:
log4cplus.appender.MyConsoleAppender=log4cplus::ConsoleAppender
log4cplus.appender.MyConsoleAppender.layout=log4cplus::PatternLayout
log4cplus.appender.MyConsoleAppender.layout.ConversionPattern=[%-5p][%d] %m%n

#MyFileAppender
log4cplus.appender.MyFileAppender=log4cplus::RollingFileAppender
log4cplus.appender.MyFileAppender.File=/tmp/logging.txt
log4cplus.appender.MyFileAppender.MaxFileSize=16MB
log4cplus.appender.MyFileAppender.MaxBackupIndex=1
log4cplus.appender.MyFileAppender.layout=log4cplus::PatternLayout
log4cplus.appender.MyFileAppender.layout.ConversionPattern=[%-5p][%D{%Y/%m/%d %H:%M:%S:%q}][%-l][%t] %m%n

Init the logger

Log4cplus need to be initialized as follow:

#include <log4cplus/logger.h>
#include <log4cplus/configurator.h>

{
::log4cplus::initialize();     ::log4cplus::PropertyConfigurator::doConfigure("log4cplus_configure.ini");
}

This code should be put in the head of the main() or in your initialization class if you have one: this is because Log4cplus must be initialized before that his instances could be used.

Code example

Let's see Log4cplus in action to understand how and when use the differents log levels:

#include <log4cplus/loggingmacros.h>

class MemoryCheck {
public: 
    MemoryCheck() 
    : logger_(log4cplus::Logger::getInstance(LOG4CPLUS_TEXT("MemoryCheck"))) 
    {}

    void alloc(...) {
        LOG4CPLUS_TRACE(logger_, "Calling alloc");

        try {
            LOG4CPLUS_DEBUG(logger_, "x = " << x);
            LOG4CPLUS_DEBUG(logger_, "y = " << y);
            /* ... */
            LOG4CPLUS_INFO(logger_, "Allocated = " << allocated_memory);
        }
        catch (std::bad_alloc& e) {
            /* I KNOW HOW TO DEAL WITH THIS ERROR!! */
            /* ...code that handle the error is here... */
            LOG4CPLUS_ERROR(logger_, e.what());
        }
        catch(boost::exception& e) {
            /* I KNOW HOW TO DEAL WITH THIS ERROR!! */
            /* ...code that handle the error is here... */
            LOG4CPLUS_ERROR(logger_, boost::diagnostic_information(e));
        }
        catch(...) {
            /* I DON'T KNOW HOW MANAGE THE ERROR!! */
            LOG4CPLUS_FATAL(logger_, "unexpected error...use gdb core dump");
            exit(-1);
        }

        if (memory_remaining < 123) {
            LOG4CPLUS_WARN(logger_, "LOW MEMORY!!");
        }
    }

private:
    log4cplus::Logger logger_;
}

Based on the previous code, we can summarize the use of each level:

  • TRACE: follow in detail all the steps of your application.
  • DEBUG: print relevant information that are vital for some part of your algorithms.
  • INFO: print an internal status of your program.
  • WARN: an internal status that can be prevented. That's why log concept exist ^__^
  • ERROR: an error has happened, but the program can handle it. It know how repair it.
  • FATAL: program can't continue! An irreversible event happened.

Conclusion

Even if there is not exist an universal agreement about the use of log level, I hope I give you the keys to understand Log4cplus and his levels.