[C++11] implement a simple log class

Basic idea:

The log class provides three basic interfaces (member functions):

  • Start log component
  • Stop() deactivates the log component
  • Commit() commit log message

Each instance of class maintains a log message queue and a resident thread;

The user starts a resident thread through the Start() function to take the log from the log message queue and save it to the disk file;

Insert a new log message into the log message queue through the Commit() function;

The thread always polls regularly to check whether there is a new log in the log message queue. If there is a new log, it will be taken out and written to the file in the form of append.

Talk is cheap. Show me the code

The following code implements a simple log class (SimpleLogger) according to the above idea

Relevant comments have been included in the code, which can be directly used in the application, but the following should be noted:

Requirements: the compilation needs to support the C + + 11 (or above) standard
Usage:

  1. Create an instance of SimpleLogger loggerobj (it is generally recommended to be a member of a business class rather than instantiated in the local scope)
  2. The program (or business) initialization phase calls loggerobj Start() function to start the log component;
  3. Call loggerobj. Where you want to print the log Commit() function to submit a log message to the log component;
  4. Loggerobj is called when the program (or business) terminates Stop() function to close the log component.

matters needing attention:

  1. This component is not a single instance. Different instances maintain different log threads;
  2. When the instance of this class is destructed, it will automatically call Stop() to close the component. Therefore, it is not recommended to create an instance locally, but as a member variable or static variable of the business class to ensure that the lifetime of the instance is long enough

Header file (SimpleLogger.h)

//SimpleLogger.h
#pragma once

#define _CRT_SECURE_NO_WARNINGS

#include <queue>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <fstream>
#include <atlstr.h>

//Simple log class SimpleLogger
//Requirements: the compilation needs to support the C + + 11 (or above) standard
//Usage:
//	*Create an instance of SimpleLogger loggerobj (it is generally recommended to be a member of a business class rather than instantiated in the local scope)
//	*The program (or business) initialization phase calls loggerobj Start() function to start the log component;
//	*Call loggerobj. Where you want to print the log Commit() function to submit a log message to the log component;
//	*Loggerobj is called when the program (or business) terminates Stop() function to close the log component.
//matters needing attention:
//This component is not a single instance. Different instances maintain different log threads;
//When the instance of this class is destructed, it will automatically call Stop() to close the component. Therefore, it is not recommended to create an instance locally, but as a member variable or static variable of the business class to ensure that the lifetime of the instance is long enough
class SimpleLogger
{
	//Internal structure definition
public:
	//Log message structure
	typedef struct LogMsg
	{
		LogMsg(const time_t t = 0, const std::string txt = "") : time(t), text(txt)	{}

		time_t time;		//Timestamp of message submission
		std::string text;	//Message text content
	}*LogMsgPtr;

	//Structure and Deconstruction
public:
	//When constructing, you need to specify the root directory of the log folder and the polling interval (ms) of the message queue. By default, it will be set to the directory where the current module is located, and the polling interval is 500ms
	SimpleLogger(std::string strDirPath = "", size_t PollingInterval = 500);
	~SimpleLogger();

	//Common interface
public:
	//Identifies whether the current component is stopped
	bool IsStoped() const { return m_bStoped; }
	//Start log component
	bool Start();
	//Stop log component
	void Stop();
	//Submit a log message
	void Commit(const char* pstrFormat, ...);

	//General interface
public:
	//Will time_t timestamp formatted as string text, default format is "% Y -% m -% d% H:% m:% s"
	static std::string FormatTimeToStr(time_t t, const char * szFormat = "%Y-%m-%d %H:%M:%S");
	//Get the folder path of the current module (with '\ \' at the end)
	static std::string GetCurModuleDir();
	//Get the file name of the current module (exe or dll name, without suffix)
	static std::string GetCurModuleName();

	//Internal interface
protected:
	//Thread function - take out the log in the message queue and save it to the disk file, resident
	static int fnPrintLog(SimpleLogger* pThis);

	//Private data
private:
	bool m_bStoped;							//Identifies whether the log component instance has stopped
	size_t m_PollingInterval;				//Log message queue polling interval (unit: ms)
	std::string m_strDirPath;				//Log folder path, which will be used as the root directory of log files
	std::queue<LogMsg> m_MsgQue;			//Log message queue
	std::mutex m_mtxMgQue;					//Log message queue access mutex
	std::condition_variable m_cvHaveMsg;	//Condition variable - whether there are new messages in log message queue
	std::thread m_thPrintLog;				//Thread object - take the log message out of the queue and save it to a file
};

Source file (SimpleLogger.cpp)

//SimpleLogger.cpp
#include "SimpleLogger.h"
#include <chrono>
#include <DbgHelp.h>
#pragma comment(lib,"DbgHelp.lib")

//When constructing, you need to specify the root directory of the log folder and the polling interval of the message queue (ms)
//Default polling interval [501000, recommended range]
SimpleLogger::SimpleLogger(std::string strDirPath/* = ""*/, size_t PollingInterval/* = 500*/)
	: m_bStoped(true)
	, m_PollingInterval(PollingInterval)
	, m_strDirPath(strDirPath)
{
	//If no root directory is specified, the default root directory will be set to the directory where the current module is located
	if (m_strDirPath.empty())
		m_strDirPath = SimpleLogger::GetCurModuleDir();
	//Append '\ \' at the end
	if (m_strDirPath.back() != '\\')
		m_strDirPath += "\\";
}

SimpleLogger::~SimpleLogger()
{
	Stop();
}

//Start log component
bool SimpleLogger::Start()
{
	if (IsStoped())
	{
		m_bStoped = false;
		m_thPrintLog = std::move(std::thread(fnPrintLog, this));//Create and start a thread that saves logs to a file
	}
	return true;
}

//Stop log component
void SimpleLogger::Stop()
{
	if (!IsStoped())
	{
		{
			//Will m_bStoped set to true indicates that you want to disable the component and wake up all log threads
			std::unique_lock<std::mutex> lock(m_mtxMgQue);
			m_bStoped = true;
		}
		//Wake up all log threads and let them end normally
		m_cvHaveMsg.notify_all();
		m_thPrintLog.join();
	}
}

//Submit a log message
void SimpleLogger::Commit(const char * pstrFormat, ...)
{
	va_list Args;
	va_start(Args, pstrFormat);
	int nLen = _vsntprintf(nullptr, 0, pstrFormat, Args);		//Get the length of the log message text first
	char *szBuffer = new char[nLen + 1];
	ZeroMemory(szBuffer, (nLen + 1) * sizeof(char));
	nLen = _vsntprintf(szBuffer, nLen + 1, pstrFormat, Args);	//Format log messages
	{
		//Insert a new log into the queue
		std::unique_lock<std::mutex> lock(m_mtxMgQue);
		m_MsgQue.push(LogMsg(::time(nullptr), szBuffer));
		//m_cvHaveMsg.notify_one();
	}
	delete[] szBuffer;
	va_end(Args);
}

//Will time_t timestamp formatted as string text, default format is "% Y -% m -% d% H:% m:% s"
std::string SimpleLogger::FormatTimeToStr(time_t t, const char * szFormat/* = "%Y-%m-%d %H:%M:%S"*/)
{
	if (!szFormat)
		return "";
	if (t < 0)
		t = 0;
	char szTime[40] = { 0 };
	struct tm local_time;
	localtime_s(&local_time, &t);
	strftime(szTime, sizeof(szTime), szFormat, &local_time);
	return std::string(szTime);
}

//Get the folder path of the current module (with '\ \' at the end)
std::string SimpleLogger::GetCurModuleDir()
{
	char szModuleName[MAX_PATH] = { 0 };
	::GetModuleFileNameA(nullptr, szModuleName, sizeof(szModuleName));
	std::string strModuleName(szModuleName);
	size_t pos = strModuleName.find_last_of('\\');
	return strModuleName.substr(0, pos + 1);
}

//Get the file name of the current module (exe or dll file name, without suffix)
std::string SimpleLogger::GetCurModuleName()
{
	char szModuleName[MAX_PATH] = { 0 };
	::GetModuleFileNameA(nullptr, szModuleName, sizeof(szModuleName));
	std::string strModuleName(szModuleName);
	size_t pos = strModuleName.find_last_of('\\');
	strModuleName.erase(0, pos + 1);
	return strModuleName.substr(0, strModuleName.size() - 4);
}

//Thread function - take out the log in the message queue and save it to the disk file, resident
int SimpleLogger::fnPrintLog(SimpleLogger* pThis)
{
	OutputDebugString(_T("MyLogger: Log thread started."));
	if (pThis)
	{
		//Construction log folder path (folder root + current module name - log \ \)
		std::string strLogDirPath = pThis->m_strDirPath + SimpleLogger::GetCurModuleName() + "-log\\";
		MakeSureDirectoryPathExists(strLogDirPath.data());
		std::string strCurLogFilePath;	//Current log save path
		std::ofstream ofsLog;			//Log file stream object
		ofsLog.imbue(std::locale("chs"));

		//Keep fetching and saving the log to a file until the external user closes the component
		while (true)
		{
			//Remove the log from the message queue
			LogMsg msg;
			{
				//Every m_PollingInterval(ms): check whether there is a new log in the current message queue. If there is a new log, take it out and save it to a file, otherwise hang it for 500ms
				std::unique_lock<std::mutex> lock(pThis->m_mtxMgQue);
				pThis->m_cvHaveMsg.wait_for(lock, std::chrono::milliseconds(pThis->m_PollingInterval),
					[pThis] { return pThis->IsStoped() || !pThis->m_MsgQue.empty(); });

				//If M_ If bstoped is true, it means that the external component has stopped and the thread should exit
				if (pThis->IsStoped() && pThis->m_MsgQue.empty())
					break;

				//Otherwise, if there are still logs to be saved in the queue, take out a log
				if (!pThis->m_MsgQue.empty())
				{
					msg = std::move(pThis->m_MsgQue.front());
					pThis->m_MsgQue.pop();
				}
				/*else
					OutputDebugString(_T("MyLogger: No new logs are currently generated "));*/
			}

			//Write current log file
			std::string strLogFilePath = strLogDirPath + FormatTimeToStr(msg.time, "%Y-%m-%d.log");
			if (strCurLogFilePath != strLogFilePath)
			{
				//The current log message submission time belongs to a new day. You should close yesterday's log file and save the new log message to today's log file
				strCurLogFilePath = strLogFilePath;
				if (ofsLog.is_open())
					ofsLog.close();
				MakeSureDirectoryPathExists(strLogFilePath.data());//Create today's log file
				ofsLog.open(strLogFilePath, std::ios_base::out | std::ios_base::app);
			}
			//Save the time and text content of the log message submission to a file
			ofsLog << FormatTimeToStr(msg.time) << "\t" << msg.text << std::endl;
		}

		//Close the currently open log file
		if (ofsLog.is_open())
			ofsLog.close();
	}

	OutputDebugString(_T("MyLogger: Log thread stopped."));
	return 0;
}

Keywords: C++

Added by persia on Wed, 02 Mar 2022 16:16:56 +0200