The Most Effective and Easy Logging Technique in C++

The Most Effective and Easy Logging Technique in C++

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(&ltm, &now);
	strftime(buf, sizeof(buf), "%Y-%m-%d %X", &ltm);
	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: