C++ Logger














































C++ Logger



C++ LOGGER MECHANISM

Description

The logging mechanism is a very useful concept for tracking the flow of any software. In this article, we will look at using a logging framework specifically build for tracing a C++ program. The article also provides the complete source code of the framework for you to use in your software!

This article contains the following sections:

  • What is a logger?
  • Introduction to the logger framework 
  • Log levels
  • Log types 
  • Working of the logger 
    • Logging using macros
    • Logging using interfaces provided by the Logger class
    • The log rotation interfaces
    • Interfaces to control log levels
    • Interfaces to control log types
    • Configuration
  • Summary 
  • Example programs
  • Source codes 
    • Logger.h
    • Logger.cpp
    • ConfigReader.h
    • ConfigReader.cpp
    • config.txt

What is a logger?

Usually, we encounter errors while developing a software project, and this phenomenon is inevitable. It's not a desirable approach to view all the lines of the code to find the fault points in our code. Also, there are instances where we desire to trace the flow of our program. A logger facilitates tracing our code.

Generally, while developing software, we add some specific lines of code in between to trace our software. In doing so, we get to know the flow of the software as those lines of code are also executed when the software executes the code below it. A logger does nothing but prints those "specific lines" of code in a separate, automatically generated file. This file is known as the log file.

Viewing the log file helps us to determine

  • the fault points in the program where the code crashes, 
  • the flow of the software,
  • the lines of codes that were successfully executed, and 
  • the lines of codes that were not executed.
Basically, it helps us a lot in the debugging process. 

Introduction to the logger framework

The logger framework described in this article is a pure C++ logging mechanism. This framework is efficient, effective and can be easily integrated with any C++ project. This is a simple-to-use framework. 

Let's have an overview of the framework. This framework supports multiple log levels and log types, which are discussed in the later sections. It also supports the log rotation mechanism for which we can explicitly set the size of a log file and the maximum number of log files to be generated. Moreover, it can also configure with a configuration file to set the desired parameters.  

Note: Log rotation mechanism is an automated process in which log files are compressed, moved, renamed, or deleted once they are too old or too big. 

For example, let us limit our log file size to 2KB and permit only at max 5 log files to be created. Then any of the following scenarios can take place. 

  1. If the log file exceeds 2KB, then the current log file is compressed and renamed as log_1.tar.gz (in the case of our framework) and a new log file is created.

  2. While performing the above-mentioned point (point 1), if there are some already compressed log files say - log_1.tar.gz and log_2.tar.gz, then the two files are renamed as tar_2.tar.gz and tar_3.tar.gz respectively. Always, the decreasing order of numbering indicates the files from the oldest to the newest. 

  3. While performing the above-mentioned point (point 2), if the number of files exceeds 5, then the oldest log file (the one named the highest number) is deleted.  


Highlighting the features of this framework:
  • Simple, effective, and easy to use.
  • Supports multiple log levels.
  • Supports multiple log types.
  • Logging with a timestamp.
  • Integrated easily into C++ projects.
  • Compatible with VC++ (on Windows platform) and g++ (on Linux platform).
  • Uses a log rotation mechanism.
  • Can be configured using a configuration file.

Logs levels

The logger mechanism provides 9 types of log levels. The following is the list of log levels with increasing priority.

  1. ENABLE_LOG - Enables all types of log levels.
  2. LOG_LEVEL_INFO
  3. LOG_LEVEL_BUFFER - This simply stores the raw data without storing timestamp and log level.
  4. LOG_LEVEL_TRACE
  5. LOG_LEVEL_DEBUG
  6. DISABLE_LOG - Disables all the levels except ALARM, ERROR, and ALWAYS.
  7. LOG_LEVEL_ALARM
  8. LOG_LEVEL_ERROR
  9. LOG_LEVEL_ALWAYS
The logging mechanism stores all the log messages having priority equal to or greater than the specified log level. For example, if we set the log level to LOG_LEVEL_DEBUG then all the messages from log levels - LOG_LEVEL_DEBUG, LOG_LEVEL_ALARM, LOG_LEVEL_ERROR, and LOG_LEVEL_ALWAYS are stored.


Log types

Three types/modes of logs are supported by the mechanism. They are:

  1. NO_LOG - In this case, the logs are not stored.
  2. CONSOLE - Prints the log to the console.
  3. FILE_LOG - Stores the log messages in an automatically generated log file.
The log type specifies whether to store the log messages or not. If logs are to be saved, then to the console or log file.

Working of the logger

Let's now see on using the logger mechanism. 

  • Firstly, the logger mechanism is defined in a namespace called 'CPlusPlusLogging'. 
  • Second, the mechanism is provided by the 'Logger' class and/or macros.
  • The logger class follows the singleton pattern; only one instance of the class is provided throughout the code. 
The class provides the getInstance() method for creating the instance of the Logger. 

static Logger* getInstance();

Note that it's a static method as the class follows the singleton pattern. 

There are two types of interfaces provided by the logger mechanism:

  1. Logging using macros
  2. Logging using interfaces provided by the Logger class

Logging using macros

There are a set of macros that allows us to log messages. The provided macros are given below. 

 LOG_ERROR(x) 
 LOG_ALARM(x)
 LOG_ALWAYS(x)
 LOG_INFO(x)
 LOG_BUFFER(x)
 LOG_TRACE(x)
 LOG_DEBUG(x)

We just need to pass the desired message to the macro. The following line shows an example of using the LOG_ERROR macro.

 LOG_ERROR("Message Logged using Direct Interface, Log level: LOG_ERROR");


Logging using interfaces provided by the Logger class

The Logger class provides us with functions for logging various messages. The following are the interfaces provided by the Logger class.

         // Interface for Error Log 
void error(const char* text) throw();
void error(std::string& text) throw();
void error(std::ostringstream& stream) throw(); // Interface for Alarm Log
void alarm(const char* text) throw();
void alarm(std::string& text) throw();
void alarm(std::ostringstream& stream) throw(); // Interface for Always Log
void always(const char* text) throw();
void always(std::string& text) throw();
void always(std::ostringstream& stream) throw(); // Interface for Buffer Log
void buffer(const char* text) throw();
void buffer(std::string& text) throw();
void buffer(std::ostringstream& stream) throw(); // Interface for Info Log
void info(const char* text) throw();
void info(std::string& text) throw();
void info(std::ostringstream& stream) throw(); // Interface for Trace log
void trace(const char* text) throw();
void trace(std::string& text) throw();
void trace(std::ostringstream& stream) throw(); // Interface for Debug log
void debug(const char* text) throw();
void debug(std::string& text) throw();
void debug(std::ostringstream& stream) throw();

One simply needs to pass the message to the function that is desired to be logged. 


The log rotation interfaces

The log rotation mechanism is controlled by two parameters - the size of the log file and the maximum number of log files. 

  1. Size of a log file - it defines the limit on log size. On exceeding this limit, a new file is created. 
  2. The maximum number of log files - specifies the maximum number of log files to be created. On exceeding this limit, the oldest file is deleted. 
The interfaces provided to control the log rotation mechanism are given below.

         // Interfaces to control roll over mechanism
void updateMaxLogFiles(const ssize_t maxFiles);
void updateLogSize(const ssize_t size);

Pass the desired figures to the functions. If the numbers passed to the functions are greater than zero, the parameters are set to the respective numbers; otherwise, the default value is set. The size of the log file is to be specified in bytes.


Interfaces to control log levels

The following are the interfaces to modify the log levels.
         // Interfaces to control log levels
void updateLogLevel(LogLevel logLevel);
void enaleLog(); // Enable all log levels
void disableLog(); // Disable all log levels, except error and alarm

The log level can be set to any of the following levels:

  1. ENABLE_LOG
  2. LOG_LEVEL_INFO
  3. LOG_LEVEL_BUFFER 
  4. LOG_LEVEL_TRACE
  5. LOG_LEVEL_DEBUG
  6. DISABLE_LOG

Interfaces to control log types

The following interfaces allow modifying the log types:
         // Interfaces to control log Types
void updateLogType(LogType logType);
void enableConsoleLogging();
void enableFileLogging();

The log type can be set to any of the following types:

  1. NO_LOG
  2. CONSOLE 
  3. FILE_LOG

It can be noted that the log levels and log types are flexible; they can be updated at any time using the interfaces. 

Configuration

Finally, let's see on configuring using a configuration file. The mechanism allows configuration using the "config.txt" file. All we need to do is set the desired parameters in the config.txt file. The supported names of the parameters in the config.txt file are as follows.
log_level - specifies the log level
log_type - specifies the log type
max_log_files - specifies the maximum number of log files
log_size - specifies the size of a log file (in bytes)

The parameters are configured using the config.txt file automatically when the instance is created or using any of the macros (whichever is done first).  


Note: The configuration involves the usage of another simple, easy-to-use framework specially built for C++. Refer to this article to know about using the framework - C++ Config Reader. However, the source code has been provided in this article for the sake of this project.  



It should be noted that if any of the parameters are missing or incorrect in the config.txt file, they are set to default values. The default values are given below. 
log_level - LOG_LEVEL_TRACE
log_type - FILE_LOG
max_log_files - 10
log_size - 3000

The log file size is set to 3000 bytes or 3KB.


Summary

So we come to an end of the discussion. We have seen all the necessities and the ways to use this logger mechanism. The logger mechanism is flexible and can be integrated into any C++ project easily. To summarise, the simple steps involved in using the framework are:

  • Set the parameters to the desired values in the config.txt file (if required)
  • Create the instance of the Logger class (not required if you wish to use the macros)
  • Use the interfaces provided for logging different types of messages 
  • Update any parameter if required at any time 
  • Get your generated log file(s) in the current directory (or get it printed to the console)
The following section provides two examples that will facilitate using this logger mechanism. 


Example programs

Here are two examples illustrating the usage of the framework. 

Example 1: 

config.txt

log_level = ENABLE_LOG             #specifying the log level
 
log_type = FILE_LOG   #specifying the log type

max_log_files = 3   #specifying the maximum number of log files

log_size = 1000           #specifying the size of a log file (in bytes)


main.cpp

#include <iostream>
#include <sstream>
#include "Logger.h"
using namespace std; using namespace CPlusPlusLogging; int main()
{
LOG_ALWAYS("<=============================== START OF PROGRAM ===============================>");
// Log message using Direct Interface
// Log Level: LOG_ERROR
LOG_ERROR("Message Logged using Direct Interface, Log level: LOG_ERROR");
LOG_ALARM("Message Logged using Direct Interface, Log level: LOG_ALARM");
LOG_ALWAYS("Message Logged using Direct Interface, Log level: LOG_ALWAYS");
LOG_INFO("Message Logged using Direct Interface, Log level: LOG_INFO");
LOG_BUFFER("Message Logged using Direct Interface, Log level: LOG_BUFFER");
LOG_TRACE("Message Logged using Direct Interface, Log level: LOG_TRACE");
LOG_DEBUG("Message Logged using Direct Interface, Log level: LOG_DEBUG"); // Log message C++ Interface
Logger* pLogger = NULL; // Create the object pointer for Logger Class
pLogger = Logger::getInstance();
pLogger->error("Message Logged using C++ Interface, Log level: LOG_ERROR");
pLogger->alarm("Message Logged using C++ Interface, Log level: LOG_ALARM");
pLogger->always("Message Logged using C++ Interface, Log level: LOG_ALWAYS");
pLogger->buffer("Message Logged using C++ Interface, Log level: LOG_INFO");
pLogger->info("Message Logged using C++ Interface, Log level: LOG_BUFFER");
pLogger->trace("Message Logged using C++ Interface, Log level: LOG_TRACE");
pLogger->debug("Message Logged using C++ Interface, Log level: LOG_DEBUG"); // Log Variables
std::string name = "Pankaj Choudhary";
std::string address = "Delhi, India";
int age = 26; std::ostringstream ss;
ss << endl;
ss << "\t" << "My Contact Details:" << endl;
ss << "\t" << "Name: " << name << endl;
ss << "\t" << "Address: " << address << endl;
ss << "\t" << "Age: " << age << endl << endl; // Log ostringstream ss to all the log levels
LOG_ALWAYS("Logging ostringstream using Direct Interface"); LOG_ERROR(ss); LOG_ALARM(ss); LOG_ALWAYS(ss); LOG_INFO(ss); LOG_BUFFER(ss); LOG_TRACE(ss); LOG_DEBUG(ss); Logger::getInstance()->buffer(
LOG_ERROR(ss);
LOG_ALARM(ss);
LOG_ALWAYS(ss);
LOG_INFO(ss);
LOG_BUFFER(ss);
LOG_TRACE(ss);
LOG_DEBUG(ss);
Logger::getInstance()->buffer("Logging ostringstream using C++ Interface"); Logger::getInstance()->error(ss); Logger::getInstance()->alarm(ss); Logger::getInstance()->always(ss); Logger::getInstance()->buffer(ss); Logger::getInstance()->info(ss); Logger::getInstance()->trace(ss); Logger::getInstance()->debug(ss);

LOG_ALWAYS("<=============================== END OF PROGRAM ===============================>");
return 0; }

Terminal commands for Linux
$ g++ -c Logger.cpp ConfigReader.cpp main.cpp
$ g++ Logger.o ConfigReader.o main.o -o log.out -pthread
$ ./log.out


Output

Three files are created - MyLogFile.log, MyLogFile_1.tar.gz, and MyLogFile_2.tar.gz

MyLogFile.log

Fri Jun 25 01:08:43 2021  [DEBUG]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:08:43 2021  [ALWAYS]: <=============================== END OF PROGRAM ===============================>

MyLogFile.log in MyLogFile_1.tar.gz

Fri Jun 25 01:01:39 2021  [TRACE]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:01:39 2021  [DEBUG]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Logging ostringstream using C++ Interface
Fri Jun 25 01:01:39 2021  [ERROR]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:01:39 2021  [ALARM]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:01:39 2021  [ALWAYS]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26



My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:01:39 2021  [INFO]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:01:39 2021  [TRACE]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26

MyLogFile.log in MyLogFile_2.tar.gz

Fri Jun 25 01:08:43 2021  [ALWAYS]: Message Logged using C++ Interface, Log level: LOG_ALWAYS
Message Logged using C++ Interface, Log level: LOG_INFO
Fri Jun 25 01:08:43 2021  [INFO]: Message Logged using C++ Interface, Log level: LOG_BUFFER
Fri Jun 25 01:08:43 2021  [TRACE]: Message Logged using C++ Interface, Log level: LOG_TRACE
Fri Jun 25 01:08:43 2021  [DEBUG]: Message Logged using C++ Interface, Log level: LOG_DEBUG
Fri Jun 25 01:08:43 2021  [ALWAYS]: Logging ostringstream using Direct Interface
Fri Jun 25 01:08:43 2021  [ERROR]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:08:43 2021  [ALARM]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:08:43 2021  [ALWAYS]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:08:43 2021  [INFO]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26



My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26

It can be noted all the levels of logs are printed. Also, since the maximum limit of files is 3, the oldest file has been deleted (the whole log would require 4 files). 

Example 2: 

config.txt
log_level = DISABLE_LOG
 
log_type = CONSOLE

main.cpp

Using the same as in Example 1.

Output
The following output is printed in the console. 

Fri Jun 25 01:53:29 2021  [ALWAYS]: <=============================== START OF PROGRAM ===============================>
Fri Jun 25 01:53:29 2021  [ERROR]: Message Logged using Direct Interface, Log level: LOG_ERROR
Fri Jun 25 01:53:29 2021  [ALARM]: Message Logged using Direct Interface, Log level: LOG_ALARM
Fri Jun 25 01:53:29 2021  [ALWAYS]: Message Logged using Direct Interface, Log level: LOG_ALWAYS
Fri Jun 25 01:53:29 2021  [ERROR]: Message Logged using C++ Interface, Log level: LOG_ERROR
Fri Jun 25 01:53:29 2021  [ALARM]: Message Logged using C++ Interface, Log level: LOG_ALARM
Fri Jun 25 01:53:29 2021  [ALWAYS]: Message Logged using C++ Interface, Log level: LOG_ALWAYS
Fri Jun 25 01:53:29 2021  [ALWAYS]: Logging ostringstream using Direct Interface
Fri Jun 25 01:53:29 2021  [ERROR]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:53:29 2021  [ALARM]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:53:29 2021  [ALWAYS]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:53:29 2021  [ERROR]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:53:29 2021  [ALARM]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:53:29 2021  [ALWAYS]: 
My Contact Details:
Name: Pankaj Choudhary
Address: Delhi, India
Age: 26


Fri Jun 25 01:53:29 2021  [ALWAYS]: <=============================== END OF PROGRAM ===============================>

It can be noted that only log levels - ALARM, ERROR, and ALWAYS are printed to the console.


Source codes

Logger.h

//////////////////////////////////////////////////////////////////////////////////////
// @File Name:     Logger.h                                                  //
// @Author:        Pankaj Choudhary                                          //
// @Version:       0.0.1                                                     //
// @L.M.D:         13th April 2015                                           //
// @Upated:        23rd June 2021 by Raj Laddha                              //
// @Description:   For Logging into file                                     //
//                                                                           // 
// Detail Description:                                                       //
// Implemented complete logging mechanism, Supporting multiple logging type  //
// like as file based logging, console base logging etc. It also supported   //
// for different log type.                                                   //
//                                                                           //
// Thread Safe logging mechanism. Compatible with VC++ (Windows platform)    //
// as well as G++ (Linux platform)                                           //
//                                                                           //
// Supported Log Type: ERROR, ALARM, ALWAYS, INFO, BUFFER, TRACE, DEBUG      //
//                                                                           //
// No control for ERROR, ALRAM and ALWAYS messages. These type of messages   //
// should be always captured.                                                //
//                                                                           //
// BUFFER log type should be use while logging raw buffer or raw messages    //
//                                                                           //
// Having direct interface as well as C++ Singleton inface. can use          //
// whatever interface want.                                                  //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#ifndef _LOGGER_H_
#define _LOGGER_H_

// C++ Header File(s)
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#ifdef WIN32
// Win Socket Header File(s)
#include <Windows.h>
#include <process.h>
#else
// POSIX Socket Header File(s)
#include <errno.h>
#include <pthread.h>
#endif

namespace CPlusPlusLogging
{
   // Direct Interface for logging into log file or console using MACRO(s)
   #define LOG_ERROR(x)    Logger::getInstance()->error(x)
   #define LOG_ALARM(x)	   Logger::getInstance()->alarm(x)
   #define LOG_ALWAYS(x)	Logger::getInstance()->always(x)
   #define LOG_INFO(x)     Logger::getInstance()->info(x)
   #define LOG_BUFFER(x)   Logger::getInstance()->buffer(x)
   #define LOG_TRACE(x)    Logger::getInstance()->trace(x)
   #define LOG_DEBUG(x)    Logger::getInstance()->debug(x)
   
   // Default value for maximum number of log files 
   #define MAX_LOG_FILES 10
   
   // Default size of a log file in bytes
   #define LOG_FILE_SIZE 3000

   // enum for LOG_LEVEL
   typedef enum LOG_LEVEL
   {
       ENABLE_LOG	= 1,
       LOG_LEVEL_INFO	= 2,
       LOG_LEVEL_BUFFER	= 3,
      LOG_LEVEL_TRACE   = 4,
      LOG_LEVEL_DEBUG   = 5,
      DISABLE_LOG	= 6
   } LogLevel;

   // enum for LOG_TYPE
   typedef enum LOG_TYPE
   {
      NO_LOG            = 1,
      CONSOLE           = 2,
      FILE_LOG          = 3,
   }LogType;

   class Logger
   {
      public:
         static Logger* getInstance() throw ();

         // Interface for Error Log 
         void error(const char* text) throw();
         void error(std::string& text) throw();
         void error(std::ostringstream& stream) throw();

         // Interface for Alarm Log 
         void alarm(const char* text) throw();
         void alarm(std::string& text) throw();
         void alarm(std::ostringstream& stream) throw();

         // Interface for Always Log 
         void always(const char* text) throw();
         void always(std::string& text) throw();
         void always(std::ostringstream& stream) throw();

         // Interface for Buffer Log 
         void buffer(const char* text) throw();
         void buffer(std::string& text) throw();
         void buffer(std::ostringstream& stream) throw();

         // Interface for Info Log 
         void info(const char* text) throw();
         void info(std::string& text) throw();
         void info(std::ostringstream& stream) throw();

         // Interface for Trace log 
         void trace(const char* text) throw();
         void trace(std::string& text) throw();
         void trace(std::ostringstream& stream) throw();

         // Interface for Debug log 
         void debug(const char* text) throw();
         void debug(std::string& text) throw();
         void debug(std::ostringstream& stream) throw();

         // Error and Alarm log must be always enable
         // Hence, there is no interfce to control error and alarm logs

         // Interfaces to control log levels
         void updateLogLevel(LogLevel logLevel);
         void enaleLog();  // Enable all log levels
         void disableLog(); // Disable all log levels, except error and alarm

         // Interfaces to control log Types
         void updateLogType(LogType logType);
         void enableConsoleLogging();
         void enableFileLogging();
         
         // Interfaces to control roll over mechanism
         void updateMaxLogFiles(const ssize_t maxFiles);
         void updateLogSize(const ssize_t size);
         

      protected:
         Logger();
         ~Logger();

         // Wrapper function for lock/unlock
         // For Extensible feature, lock and unlock should be in protected
         void lock();
         void unlock();

         std::string getCurrentTime();

      private:
         void logIntoFile(std::string& data);
         void logOnConsole(std::string& data);
         Logger(const Logger& obj) {}
         void operator=(const Logger& obj) {}
         void rollLogFiles();
         void configure();

      private:
         static Logger*          m_Instance;
         std::ofstream           m_File;

#ifdef	WIN32
         CRITICAL_SECTION        m_Mutex;
#else
         pthread_mutexattr_t     m_Attr; 
         pthread_mutex_t         m_Mutex;
#endif

         LogLevel                m_LogLevel;
         LogType                 m_LogType;
          
         unsigned int		 logSize; // Size of a log file in bytes
         unsigned int		 maxLogFiles; // Maximum number of log files
         unsigned int		 logFilesCount; // Count of existing log files 
         
         
         
   };

} // End of namespace

#endif // End of _LOGGER_H_

Logger.cpp

///////////////////////////////////////////////////////////////////////////////
// @File Name:     Logger.cpp                                                //
// @Author:        Pankaj Choudhary                                          //
// @Version:       0.0.1                                                     //
// @L.M.D:         13th April 2015                                           //
// @Upated:        23rd June 2021 by Raj Laddha                              //
// @Description:   For Logging into file                                     //
//                                                                           // 
// Detail Description:                                                       //
// Implemented complete logging mechanism, Supporting multiple logging type  //
// like as file based logging, console base logging etc. It also supported   //
// for different log type.                                                   //
//                                                                           //
// Thread Safe logging mechanism. Compatible with VC++ (Windows platform)    //
// as well as G++ (Linux platform)                                           //
//                                                                           //
// Supported Log Type: ERROR, ALARM, ALWAYS, INFO, BUFFER, TRACE, DEBUG      //
//                                                                           //
// No control for ERROR, ALRAM and ALWAYS messages. These type of messages   //
// should be always captured.                                                //
//                                                                           //
// BUFFER log type should be use while logging raw buffer or raw messages    //
//                                                                           //
// Having direct interface as well as C++ Singleton inface. can use          //
// whatever interface want.                                                  //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

// C++ Header File(s)
#include <iostream>
#include <cstdlib>
#include <ctime>

// Code Specific Header Files(s)
#include "Logger.h"
#include "ConfigReader.h"

using namespace std;
using namespace CPlusPlusLogging;
using namespace cppsecrets;

Logger* Logger::m_Instance = 0;

// Log file name. File name should be change from here only
const string logFileName = "MyLogFile.log";

Logger::Logger()
{
   m_File.open(logFileName.c_str(), ios::out|ios::app);
   configure();
   logFilesCount  = 1;

   // Initialize mutex
#ifdef WIN32
   InitializeCriticalSection(&m_Mutex);
#else
   int ret=0;
   ret = pthread_mutexattr_settype(&m_Attr, PTHREAD_MUTEX_ERRORCHECK_NP);
   if(ret != 0)
   {   
      printf("Logger::Logger() -- Mutex attribute not initialize!!\n");
      exit(0);
   }   
   ret = pthread_mutex_init(&m_Mutex,&m_Attr);
   if(ret != 0)
   {   
      printf("Logger::Logger() -- Mutex not initialize!!\n");
      exit(0);
   }   
#endif
}

Logger::~Logger()
{
   m_File.close();
#ifdef WIN32
   DeleteCriticalSection(&m_Mutex);
#else
   pthread_mutexattr_destroy(&m_Attr);
   pthread_mutex_destroy(&m_Mutex);
#endif
}

Logger* Logger::getInstance() throw ()
{
   if (m_Instance == 0) 
   {
      m_Instance = new Logger();
   }
   return m_Instance;
}

void Logger::lock()
{
#ifdef WIN32
   EnterCriticalSection(&m_Mutex);
#else
   pthread_mutex_lock(&m_Mutex);
#endif
}

void Logger::unlock()
{
#ifdef WIN32
   LeaveCriticalSection(&m_Mutex);
#else
   pthread_mutex_unlock(&m_Mutex);
#endif
}

void Logger::logIntoFile(std::string& data)
{
   unsigned long pos = m_File.tellp();
   if(pos + data.size() > logSize)
   {
		rollLogFiles();	
   }
   
   lock();
   m_File << getCurrentTime() << "  " << data << endl;
   unlock();
}

void Logger::logOnConsole(std::string& data)
{
   cout << getCurrentTime() << "  " << data << endl;
}

string Logger::getCurrentTime()
{
   string currTime;
   //Current date/time based on current time
   time_t now = time(0); 
   // Convert current time to string
   currTime.assign(ctime(&now));

   // Last charactor of currentTime is "\n", so remove it
   string currentTime = currTime.substr(0, currTime.size()-1);
   return currentTime;
}

// Interface for Error Log
void Logger::error(const char* text) throw()
{		
   string data;
   data.append("[ERROR]: ");
   data.append(text);

   // ERROR must be capture
   if(m_LogType == FILE_LOG && m_LogLevel)
   {
      logIntoFile(data);
   }
   else if(m_LogType == CONSOLE && m_LogLevel)
   {
      logOnConsole(data);
   }
}

void Logger::error(std::string& text) throw()
{
   error(text.data());
}

void Logger::error(std::ostringstream& stream) throw()
{
   string text = stream.str();
   error(text.data());
}

// Interface for Alarm Log 
void Logger::alarm(const char* text) throw()
{
   string data;
   data.append("[ALARM]: ");
   data.append(text);

   // ALARM must be capture
   if(m_LogType == FILE_LOG && m_LogLevel)
   {
      logIntoFile(data);
   }
   else if(m_LogType == CONSOLE && m_LogLevel)
   {
      logOnConsole(data);
   }
}

void Logger::alarm(std::string& text) throw()
{
   alarm(text.data());
}

void Logger::alarm(std::ostringstream& stream) throw()
{
   string text = stream.str();
   alarm(text.data());
}

// Interface for Always Log 
void Logger::always(const char* text) throw()
{
   string data;
   data.append("[ALWAYS]: ");
   data.append(text);

   // No check for ALWAYS logs
   if(m_LogType == FILE_LOG && m_LogLevel)
   {
      logIntoFile(data);
   }
   else if(m_LogType == CONSOLE && m_LogLevel)
   {
      logOnConsole(data);
   }
}

void Logger::always(std::string& text) throw()
{
   always(text.data());
}

void Logger::always(std::ostringstream& stream) throw()
{
   string text = stream.str();
   always(text.data());
}

// Interface for Buffer Log 
void Logger::buffer(const char* text) throw()
{
   // Buffer is the special case. So don't add log level
   // and timestamp in the buffer message. Just log the raw bytes.
   if((m_LogType == FILE_LOG) && (m_LogLevel <= LOG_LEVEL_BUFFER))
   {
      lock();
      m_File << text << endl;
      unlock();
   }
   else if((m_LogType == CONSOLE) && (m_LogLevel <= LOG_LEVEL_BUFFER))
   {
      cout << text << endl;
   }
}

void Logger::buffer(std::string& text) throw()
{
   buffer(text.data());
}

void Logger::buffer(std::ostringstream& stream) throw()
{
   string text = stream.str();
   buffer(text.data());
}

// Interface for Info Log
void Logger::info(const char* text) throw()
{
   string data;
   data.append("[INFO]: ");
   data.append(text);

   if((m_LogType == FILE_LOG) && (m_LogLevel <= LOG_LEVEL_INFO))
   {
      logIntoFile(data);
   }
   else if((m_LogType == CONSOLE) && (m_LogLevel <= LOG_LEVEL_INFO))
   {
      logOnConsole(data);
   }
}

void Logger::info(std::string& text) throw()
{
   info(text.data());
}

void Logger::info(std::ostringstream& stream) throw()
{
   string text = stream.str();
   info(text.data());
}

// Interface for Trace Log
void Logger::trace(const char* text) throw()
{
   string data;
   data.append("[TRACE]: ");
   data.append(text);

   if((m_LogType == FILE_LOG) && (m_LogLevel <= LOG_LEVEL_TRACE))
   {
      logIntoFile(data);
   }
   else if((m_LogType == CONSOLE) && (m_LogLevel <= LOG_LEVEL_TRACE))
   {
      logOnConsole(data);
   }
}

void Logger::trace(std::string& text) throw()
{
   trace(text.data());
}

void Logger::trace(std::ostringstream& stream) throw()
{
   string text = stream.str();
   trace(text.data());
}

// Interface for Debug Log
void Logger::debug(const char* text) throw()
{
   string data;
   data.append("[DEBUG]: ");
   data.append(text);

   if((m_LogType == FILE_LOG) && (m_LogLevel <= LOG_LEVEL_DEBUG))
   {
      logIntoFile(data);
   }
   else if((m_LogType == CONSOLE) && (m_LogLevel <= LOG_LEVEL_DEBUG))
   {
      logOnConsole(data);
   }
}

void Logger::debug(std::string& text) throw()
{
   debug(text.data());
}

void Logger::debug(std::ostringstream& stream) throw()
{
   string text = stream.str();
   debug(text.data());
}

// Interfaces to control log levels
void Logger::updateLogLevel(LogLevel logLevel)
{
   m_LogLevel = logLevel;
}

// Enable all log levels
void Logger::enaleLog()
{
   m_LogLevel = ENABLE_LOG; 
}

// Disable all log levels, except error and alarm
void Logger:: disableLog()
{
   m_LogLevel = DISABLE_LOG;
}

// Interfaces to control log Types
void Logger::updateLogType(LogType logType)
{
   m_LogType = logType;
}

void Logger::enableConsoleLogging()
{
   m_LogType = CONSOLE; 
}

void Logger::enableFileLogging()
{
   m_LogType = FILE_LOG ;
}

// Interfaces to control roll over mechanism
void Logger::updateMaxLogFiles(const ssize_t maxFiles)
{
    if(maxFiles > 0)
	maxLogFiles = maxFiles;

    else
	maxLogFiles = MAX_LOG_FILES;

}

void Logger::updateLogSize(const ssize_t size)
{
    if(size > 0)
	logSize = size;

    else
        logSize = LOG_FILE_SIZE;
}

// Handle roll over mechanism
void Logger::rollLogFiles()
{
    m_File.close();	

    if(maxLogFiles > 1)
    {
         string logFile = logFileName.substr(0, logFileName.length()-4); // removing ".log" from file name
   
         // To check if the maximum files have reached
         if(logFilesCount >= maxLogFiles)
         {
             string deleteFileName = logFile + "_" + to_string(maxLogFiles-1) + ".tar.gz";
             remove(deleteFileName.c_str());
    
             logFilesCount--; 	
         }

         // Renaming the files 
        for(int i = logFilesCount; i >= 2; --i)
       {
             string oldLogFileName = logFile + "_" + to_string(i-1) + ".tar.gz";
             string newLogFileName = logFile + "_" + to_string(i) + ".tar.gz";

             rename(oldLogFileName.c_str(), newLogFileName.c_str());
         }

         string cmd = "tar -cf " + logFile + "_1.tar.gz " + logFileName;

        system(cmd.c_str()); // creating tar file
    }

    remove(logFileName.c_str());

    m_File.open(logFileName.c_str(), ios::out|ios::app);

    if(logFilesCount < maxLogFiles)
    {
        logFilesCount++;
    }
}

// For configuration
// Note: The function sets the default parameters if any paramter is incorrect or missing  
void Logger::configure()
{
    ConfigReader* config = ConfigReader::getInstance();

    config->parseFile();
 
    LogLevel logLevel;
    LogType logType;

    string logLevel_str;
    string logType_str;

    int logFiles;
    int logFileSize;

    // Configuring the log level
    if( config->getValue("log_level", logLevel_str) )
    {
        if(logLevel_str == "ENABLE_LOG" || logLevel_str == "1")
            logLevel = ENABLE_LOG;

        else if(logLevel_str == "LOG_LEVEL_INFO" || logLevel_str == "2")
            logLevel = LOG_LEVEL_INFO;

        else if(logLevel_str == "LOG_LEVEL_BUFFER" || logLevel_str == "3")
            logLevel = LOG_LEVEL_BUFFER;

        else if(logLevel_str == "LOG_LEVEL_TRACE" || logLevel_str == "4")
            logLevel = LOG_LEVEL_TRACE;

        else if(logLevel_str == "LOG_LEVEL_DEBUG" || logLevel_str == "5")
            logLevel = LOG_LEVEL_DEBUG;

        else if(logLevel_str == "DISABLE_LOG" || logLevel_str == "6")
            logLevel = DISABLE_LOG;

        else
            logLevel = LOG_LEVEL_TRACE;
    }

    else
        logLevel = LOG_LEVEL_TRACE;


    // Configuring the log type
    if( config->getValue("log_type", logType_str) )
    {
        if(logType_str == "NO_LOG" || logType_str == "1")
            logType = NO_LOG;
 
        else if(logType_str == "CONSOLE" || logType_str == "2")
            logType = CONSOLE;

        else
            logType = FILE_LOG;
    }

    else
        logType = FILE_LOG;



    if( !config->getValue("max_log_files", logFiles) )
        logFiles = MAX_LOG_FILES;

    if( !config->getValue("log_size", logFileSize) )
        logFileSize = LOG_FILE_SIZE;


    // Setting the parameters
    m_LogLevel = logLevel;
    m_LogType = logType;
    
    updateMaxLogFiles(logFiles);

    updateLogSize(logFileSize);

}

ConfigReader.h

#ifndef _CONFIG_READER_H_
#define _CONFIG_READER_H_

// C++ Header File(s)
#include <iostream>
#include <map>

// Define namespace for this class. so that anyonelo
// can easily integrate it. The class name is vary generic
// so the chance to collapse the class name is high.
// So need to define the class inside the namespace.

namespace cppsecrets
{
   // Defining ConfigReader as singleton class
   // Easy to access across the multiple classes
   //
   // The responsibility of this class is to parse the
   // Config file and store it in the std::map
   // Defined getter function getValue() to get the
   // data from the std::map.
   //
   // To use this class, pass the config file path to
   // the function getInstance()
   //
   // This is one of the advance config reader, because this
   // class is handling the comment line as well. Comment line 
   // will start from hash(#). So all the string after
   // semicolon will be discarded.
   //
   // NOTE: NO NEED TO MAKE THIS CLASS THREAD-SAFE. IT IS EXTRA OVEHEAD.
   // BECAUSE MOSTLY WE ARE DOING ONLY READ OPERATION. WRITE OPERATION IS
   // HAPPENING ONLY ONE TIME, WHICH IS IN THE FUNCTION parse(). SO CALL
   // parse() FUNCTION AT THE TIME OF INITIALIZATION ONLY.
   //
   // IF YOUR CONFIGURATION FILE IS UPDATING AT THE RUN TIME AND YOU NEED
   // UPDATED DATA FROM THE CONFIGURATION FILE AT RUN TIME, THEN YOU NEED 
   // TO MAKE THIS CLASS THREAD-SAFE.

   class ConfigReader
   {
      private:
         
         // Define the map to store data from the config file
         std::map<std::string, std::string> m_ConfigSettingMap;

         // Static pointer instance to make this class singleton.
         static ConfigReader* m_pInstance;

      public:
         
         // Public static method getInstance(). This function is
         // responsible for object creation.
         static ConfigReader* getInstance();

         // Parse the config file.
         bool parseFile(std::string fileName = "config.txt");

         // Overloaded getValue() function.
         // Value of the tag in cofiguration file could be
         // string or integer. So the caller need to take care this.
         // Caller need to call appropiate function based on the
         // data type of the value of the tag.

         bool getValue(std::string tag, int& value);
         bool getValue(std::string tag, std::string& value);

         // Function dumpFileValues is for only debug purpose
         void dumpFileValues();

      private:

         // Define constructor in the private section to make this
         // class as singleton.
         ConfigReader();

         // Define destructor in private section, so no one can delete 
         // the instance of this class.
         ~ConfigReader();

         // Define copy constructor in the private section, so that no one can 
         // voilate the singleton policy of this class
         ConfigReader(const ConfigReader& obj){}
         
         // Define assignment operator in the private section, so that no one can 
         // voilate the singleton policy of this class
         void operator=(const ConfigReader& obj){}

         // Helper function to trim the tag and value. These helper function is
         // calling to trim the un-necessary spaces.
         std::string trim(const std::string& str, const std::string& whitespace = " \t");
         std::string reduce(const std::string& str,
               const std::string& fill = " ",
               const std::string& whitespace = " \t");
   };

} //End of namespace

#endif // End of _CONFIG_READER_H_


ConfigReader.cpp

// Code Specific Header File(s)
#include "ConfigReader.h"

// C++ Header File(s)
#include <fstream>
#include <algorithm>

using namespace std;
using namespace cppsecrets;

#define NULL_PTR 0

ConfigReader* ConfigReader::m_pInstance = NULL_PTR;

ConfigReader::ConfigReader()
{
   m_ConfigSettingMap.clear();
}

ConfigReader::~ConfigReader()
{
   m_ConfigSettingMap.clear();
}

ConfigReader* ConfigReader::getInstance()
{
   // No need to use double re-check lock mechanism here
   // because this getInstance() will call at the time of
   // initialization only and mostly, at the time of
   // initialization, there will be only one thread.

   if(NULL_PTR == m_pInstance)
   {
      m_pInstance = new ConfigReader;
   }
   return m_pInstance;
}

bool ConfigReader::getValue(std::string tag, int& value)
{
   map<string, string>::iterator it ;
   it = m_ConfigSettingMap.find(tag);
   if(it != m_ConfigSettingMap.end())
   {
      value = atoi((it->second).c_str());
      return true;
   }
   return false;
}

bool ConfigReader::getValue(std::string tag, std::string& value)
{
   map<string, string>::iterator it ;
   it = m_ConfigSettingMap.find(tag);
   if(it != m_ConfigSettingMap.end())
   {
      value = it->second;
      return true;
   }
   return false;
}

bool ConfigReader::parseFile(string fileName)
{
   ifstream inputFile;
   inputFile.open(fileName.c_str());
   string delimeter = "=";
   int initPos = 0;

   if (inputFile.fail())
   {
      cout << "Unable to find defaultConfig file" << endl;
      return false;
   }

   string line;
   while(getline(inputFile, line))
   {
      // Remove comment Lines
      size_t found = line.find_first_of('#');
      string configData = line.substr(0, found);

      // Remove ^M from configData
      configData.erase(std::remove(configData.begin(), configData.end(), '\r'), configData.end());

      if(configData.empty())
         continue;

      unsigned int length = configData.find(delimeter);

      string tag, value;

      if (length!=string::npos)
      {
         tag   = configData.substr(initPos, length);
         value = configData.substr(length+1);
      }

      // Trim white spaces
      tag   = reduce(tag);
      value = reduce(value);
      
      if(tag.empty() || value.empty())
         continue;

      // Check if any of the tags is repeated more than one times
      // it needs to pick the latest one instead of the old one.

      // Search, if the tag is already present or not
      // If it is already present, then delete an existing one

      std::map<std::string, std::string>::iterator itr = m_ConfigSettingMap.find(tag);
      if(itr != m_ConfigSettingMap.end())
      {
         m_ConfigSettingMap.erase(tag);
      }

      m_ConfigSettingMap.insert(std::pair<string, string>(tag, value));
   }
   return true;
}

std::string ConfigReader::trim(const std::string& str, const std::string& whitespace)
{
   size_t strBegin = str.find_first_not_of(whitespace);
   if (strBegin == std::string::npos)
      return "";

   size_t strEnd = str.find_last_not_of(whitespace);
   size_t strRange = strEnd - strBegin + 1;

   return str.substr(strBegin, strRange);
}

std::string ConfigReader::reduce(const std::string& str,
      const std::string& fill,
      const std::string& whitespace)
{
   // trim first
   string result = trim(str, whitespace);

   // replace sub ranges
   size_t beginSpace = result.find_first_of(whitespace);
   while (beginSpace != std::string::npos)
   {
      size_t endSpace = result.find_first_not_of(whitespace, beginSpace);
      size_t range = endSpace - beginSpace;

      result.replace(beginSpace, range, fill);

      size_t newStart = beginSpace + fill.length();
      beginSpace = result.find_first_of(whitespace, newStart);
   }

   return result;
}

void ConfigReader::dumpFileValues()
{
   map<string, string>::iterator it;
   for(it=m_ConfigSettingMap.begin(); it!=m_ConfigSettingMap.end(); ++it)
   {
      cout << it->first << " = " << it->second << endl;
   }
}

config.txt

log_level = 
 
log_type =

max_log_files =

log_size =

  

Comments