Logging is an essential part of any application. While implementing a logging system is not the most exciting task, it will benefit you in the long run if you take the time to develop a good solution for it. I designed a new logging system for my C++ program. It’s just as easy and adaptable to incorporate. You should make a habit of logging the required information in your code. It will come in handy later and save you a lot of time if you need to track down an error, warning or information.
Files required for logging:
1. MPLogger.h
#pragma once
//Header
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
#include <process.h>
#include <Windows.h>
namespace namespace_MPLogger
{
#define MPLOG_TRACE(x, level) MPLogger::getLoggerInstance()->log_trace(x, level)
enum class eMPLOG_LEVEL
{
LOG_LEVEL_ALL = 0,
LOG_LEVEL_CRITICAL= 1,
LOG_LEVEL_ERROR=2,
LOG_LEVEL_WARNING=3,
LOG_LEVEL_INFO=4,
ACTIVATE_LOG=5,
DEACTIVATE_LOG=6
};
class MPLogger
{
private:
static std::unique_ptr<MPLogger> m_pLogInstance;
std::ofstream m_LogFile;
eMPLOG_LEVEL m_eMPLogLevel;
eMPLOG_LEVEL m_eMPLogStatus;
CRITICAL_SECTION m_MutexLog;
MPLogger();
void logIntoFile(std::string& data);
std::string getCurrentTime();
MPLogger(const MPLogger& obj) = delete;
void operator=(const MPLogger& obj) = delete;
public:
~MPLogger();
static std::unique_ptr<MPLogger>& getLoggerInstance();
// API for logging
void log_trace(const char* text, eMPLOG_LEVEL level);
void log_trace(std::string& text, eMPLOG_LEVEL level);
void log_trace(std::ostringstream& stream, eMPLOG_LEVEL level);
// Config logging
void setLogLevel(eMPLOG_LEVEL MPlogLevel);
void activateLog(); // Activate logging
void deactivateLog(); // Deactivate logging
protected:
void lock();
void unlock();
};
};
2. MPLogger.cpp
#include "MPLogger.h"
#include <cstdlib>
#include <iostream>
#include <ctime>
#include<mutex>
#include<chrono>
//Namespace
using namespace std;
using namespace namespace_MPLogger;
unique_ptr<MPLogger> MPLogger::m_pLogInstance = nullptr;
std::once_flag gflag_once;
//Can be passed as a parameter
const string logFileName = "MPLog.log";
MPLogger::MPLogger()
{
m_LogFile.open(logFileName.c_str(), ios::out | ios::app);
//Hardcoded - Set Log level from configuration
m_eMPLogLevel = eMPLOG_LEVEL::LOG_LEVEL_INFO;
m_eMPLogStatus = eMPLOG_LEVEL::ACTIVATE_LOG;
// set/Initialize mutex
InitializeCriticalSection(&m_MutexLog);
}
MPLogger::~MPLogger()
{
m_LogFile.close();
DeleteCriticalSection(&m_MutexLog);
}
unique_ptr<MPLogger>& MPLogger::getLoggerInstance()
{
// Acts as a singleton
std::call_once(gflag_once, [](){
m_pLogInstance = unique_ptr<MPLogger>(new MPLogger());
});
return m_pLogInstance;
}
void MPLogger::lock()
{
EnterCriticalSection(&m_MutexLog);
}
void MPLogger::unlock()
{
LeaveCriticalSection(&m_MutexLog);
}
// Interface for Trace Log
void MPLogger::log_trace(const char* text, eMPLOG_LEVEL level)
{
string data;
if (eMPLOG_LEVEL::ACTIVATE_LOG == m_eMPLogStatus)
{
if (eMPLOG_LEVEL::LOG_LEVEL_ALL == m_eMPLogLevel)
{
// print info logs only when enabled from configuration
if (level == eMPLOG_LEVEL::LOG_LEVEL_INFO ||
eMPLOG_LEVEL::LOG_LEVEL_INFO == eMPLOG_LEVEL::LOG_LEVEL_ALL) {
logIntoFile(data.append("[INFO]: ").append(text));
}
}
//Print always critical, error and warnings
if (level == eMPLOG_LEVEL::LOG_LEVEL_CRITICAL ||
eMPLOG_LEVEL::LOG_LEVEL_CRITICAL == eMPLOG_LEVEL::LOG_LEVEL_ALL) {
logIntoFile(data.append("[CRITICAL]: ").append(text));
}
if (level == eMPLOG_LEVEL::LOG_LEVEL_ERROR ||
eMPLOG_LEVEL::LOG_LEVEL_ERROR == eMPLOG_LEVEL::LOG_LEVEL_ALL) {
logIntoFile(data.append("[ERROR]: ").append(text));
}
if (level == eMPLOG_LEVEL::LOG_LEVEL_WARNING ||
eMPLOG_LEVEL::LOG_LEVEL_WARNING == eMPLOG_LEVEL::LOG_LEVEL_ALL) {
logIntoFile(data.append("[WARNING]: ").append(text));
}
}
}
void MPLogger::log_trace(std::string& text, eMPLOG_LEVEL level)
{
log_trace(text.data(), level);
}
void MPLogger::log_trace(std::ostringstream& stream, eMPLOG_LEVEL level)
{
string text = stream.str();
log_trace(text.data(), level);
}
void MPLogger::activateLog()
{
m_eMPLogStatus = eMPLOG_LEVEL::ACTIVATE_LOG;
}
void MPLogger::deactivateLog()
{
m_eMPLogStatus = eMPLOG_LEVEL::DEACTIVATE_LOG;
}
void MPLogger::logIntoFile(std::string& data)
{
lock();
m_LogFile << getCurrentTime() << " " << this_thread::get_id() << " " << data << endl;
unlock();
}
std::string MPLogger::getCurrentTime()
{
struct tm tstruct;
char buf[80];
time_t now = time(0);
tm ltm;
localtime_s(<m, &now);
strftime(buf, sizeof(buf), "%Y-%m-%d %X", <m);
return buf;
}
Allowing logs to be enabled in a few systems all of the time can result in massive log files. To circumvent this, logging should be done at various levels as needed.
m_eMPLogLevel = <read log level from configuration>
Setting log levels:
Set the log level via settings in the function MPLogger::MPLogger(). As a result, the user will be able to specify multiple log levels within the code as needed.
Within the system, enable or disable logging.
m_eMPLogStatus = <read log status from configuration>
Sample C++ client application to print or trace logs:
#include <iostream>
#include "MPLogger.h"
#include <thread>
using namespace namespace_MPLogger;
void WriteLogs()
{
for (int i = 0; i < 1000; i++)
{
std::string val = "Hello";
val = val.append(std::to_string(i));
MPLOG_TRACE(val, eMPLOG_LEVEL::LOG_LEVEL_CRITICAL);
}
}
int main()
{
std::cout << "Hello World!\n";
std::thread t1(WriteLogs);
std::thread t2(WriteLogs);
t1.join();
t2.join();
return 0;
}
Popular post: