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:
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 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.
The logger mechanism provides 9 types of log levels. The following is the list of log levels with increasing priority.
Three types/modes of logs are supported by the mechanism. They are:
Let's now see on using the logger mechanism.
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:
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");
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 mechanism is controlled by two parameters - the size of the log file and the maximum number of log files.
// 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
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:
// 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:
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.
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:
Here are two examples illustrating the usage of the framework.
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;
}
$ 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 ===============================>
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
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
log_level = DISABLE_LOG
log_type = 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 ===============================>
//////////////////////////////////////////////////////////////////////////////////////
// @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_
///////////////////////////////////////////////////////////////////////////////
// @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);
}
#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_
// 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;
}
}
log_level = log_type = max_log_files = log_size =
Comments