Простой логгер для C++

в 12:29, , рубрики: c++, logger, лог, Программирование, метки: , ,

Недавно я искал удобный, простой и в то же время функциональный логгер для своих приложений. В принципе, для этого уже существует много фреймворков. Но я искал ни сколько доступный для копипастинга код, сколько сами подходы к ведению лога. Спустя некоторое время я наткнулся на Log 4 C++ — замечательный фреймворк. Представленный в этой статье класс, для ведения лога, основан на идеях Log 4 C++.

Большинство подобных фреймоворков предоставляют еще множество тонких настроек и опций, которые не были включены мной в мой небольшой класс, что и делает его простым в использовании. Весь код вписывается в один единственный .h файл и легко понятен, поэтому может быть легко изменен под конкретные нужды.
Код использует часть функций С++11, однако, заставить его работать со старой спецификацией не составит труда..

Разработка

CLogger — главный класс, с которым мы будем работать. Он находится в пространстве имен framework::Diagnostics. Функции для синхронизации потоков предоставляются другими классами, расположенными в пространстве имен framework::Threading. Один из них — CIntraProcessLock, используется, если экземпляр класса CLogger совместно используется разными потоками в пределах одного процесса. Если многопоточное использование не предвидится — используйте CNoLock. При создании экземпляра логгера, вы должны, в качестве параметра шаблона, предоставить один из этих классов, либо любой другой, имеющий методы Lock() и Unlock().

Использование

Пример создания логгера:

using namespace framework::Diagnostics;
using namespace framework::Threading;
CLogger<CNolock> logger(LogLevel::Info, _T("MyApp"));

Конструктор имеет следующие параметры:

  • LogLevel — может быть одним из значений перечисления framework::Diagnostics::LogLevel. Это значение используется, если вы добавляете поток вывода, оно определяет, что будет записываться в лог.
  • Name — имя, данное объекту логгера, которое будет записываться в каждую строку журнала.
  • logItems — это битовая маска списка элементов, которые необходимо выводить в каждой строке журнала. Значения берутся из перечисления framework::Diagnostics::LogItem. По умолчанию, это настроено так:
    Простой логгер для C++
    Таким образом, если вы, например, хотите, чтобы в лог выводились только дата и время, создание экземпляра приняло бы следующий вид:

    CLogger<CNoLock> logger(LogLevel::Info, _T("MyApp"), static_cast<int>(LogItem::DateTime));
    

Следующим шагом необходимо добавить поток вывода. Каждому потоку вывода вы можете назначить «уровень», например уровень Info выводить в cout, а Error — в файл. Пример:

logger.AddOutputStream(std::wcout, false, LogLevel::Error);
logger.AddOutputStream(new std::wofstream("c:\temp\myapp.log"), true, framework::Diagnostics::LogLevel::Info);

Метод AddOutputStream имеет следующие параметры:

  • os — принимает любой производный класс ostream, например cout или wcout.
  • own — если true, экземпляр CLogger будет использовать оператор delete, для удаления переданного объекта ostream. Это обычно полезно, если вы создаете новый безымянный экземпляр filestream в качестве параметра (см. код выше).
  • LogLevel — все записи, равные или выше этого уровня будут записаны в поток вывода. По умолчанию будет использоваться уровень CLogger.

Ну и, наконец, чтобы осуществить запись в лог, необходимо использовать макрос WRITELOG. Пример:

WRITELOG(logger, framework::Diagnostics::LogLevel::Info, _T("Program starting"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Warn, _T("Something may have gone wrong"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Error, _T("Something did go wrong"));
WRITELOG(logger, framework::Diagnostics::LogLevel::Info, _T("Program Ending"));

Исходный код

main.cpp

#include "threading.h"
#include "Logger.h"
#include <fstream>

using namespace framework::Diagnostics;
using namespace framework::Threading;

int _tmain(int argc, _TCHAR* argv[])
{
    CLogger<CNolock> logger(LogLevel::Info, _T("MyApp"));

    logger.AddOutputStream(std::wcout, false, LogLevel::Error);
    logger.AddOutputStream(new std::wofstream("c:\temp\myapp.log"), true, framework::Diagnostics::LogLevel::Info);
        
    WRITELOG(logger, framework::Diagnostics::LogLevel::Info, _T("Program starting"));
    WRITELOG(logger, framework::Diagnostics::LogLevel::Warn, _T("Something may have gone wrong"));
    WRITELOG(logger, framework::Diagnostics::LogLevel::Error, _T("Something did go wrong"));
    WRITELOG(logger, framework::Diagnostics::LogLevel::Info, _T("Program Ending"));

    return 0;
}

tstring.h

#ifndef TSTRING_H_DEFINED
#define TSTRING_H_DEFINED

#include <string>
#include <tchar.h>

#if defined(UNICODE) || defined(_UNICODE)
	#define tstring std::wstring
	#define tout std::wcout
	#define tin std::wcin
	#define tostream std::wostream
	#define tofstream std::wofstream
	#define tifstream std::wifstream
	#define tfstream std::wfstream
#else
	#define tstring std::string
	#define tout std::cout
	#define tin std::cin
	#define tostream std::ostream
	#define tofstream std::ofstream
	#define tifstream std::ifstream
	#define tfstream std::fstream
#endif

#endif

threading.h

#pragma once

#include <windows.h>

namespace framework
{
	namespace Threading
	{
		class CIntraProcessLock
		{
		public:
			CIntraProcessLock()
			{
				InitializeCriticalSection(&m_cs);
			}
			
			~CIntraProcessLock()
			{
				DeleteCriticalSection(&m_cs);
			}

			inline bool Lock()
			{
				return TryEnterCriticalSection(&m_cs) > 0 ? true : false;
			}

			inline void Unlock()
			{
				return LeaveCriticalSection(&m_cs);
			}

		private:
			CRITICAL_SECTION m_cs;
		};

		class CNoLock
		{
		public:
			inline bool Lock() 
			{ 
				return true; 
			}
			
			inline void Unlock()
			{
				return;
			}
		};
	}
}

Logger.h

#pragma once

#include <iostream>
#include <vector>
#include <stdio.h>
#include <Windows.h>
#include <time.h>
#include "tstring.h"
#include "threading.h"

#ifdef UNICODE
#define WRITELOG(logObj, level, text) logObj.Log(level, __FILEW__, __LINE__, __FUNCTIONW__, text);
#else
#define WRITELOG(logObj, level, text) logObj.Log(level, __FILEW__, __LINE__, __FUNCTION__, text);
#endif

using namespace std;

namespace framework
{	
	namespace Diagnostics
	{
		enum class LogLevel
		{
			Info,		
			Debug,
			Warn,
			Error
		};

		enum class LogItem
		{
			Filename	= 0x1,
			LineNumber	= 0x2,
			Function    = 0x4,
			DateTime	= 0x8,		
			ThreadId	= 0x10,
			LoggerName  = 0x20,
			LogLevel	= 0x40
		};

		template <class ThreadingProtection> class CLogger
		{
		private:
			struct StreamInfo
			{
				tostream* pStream;
				bool owned;
				LogLevel level;

				StreamInfo(wostream* pStream, bool owned, LogLevel level)
				{
					this->pStream = pStream;
					this->owned = owned;
					this->level = level;
				}
			};

		public:
			CLogger(framework::Diagnostics::LogLevel level, LPCTSTR name, 
				int loggableItems = static_cast<int>(LogItem::Function) | static_cast<int>(LogItem::LineNumber) | 
				static_cast<int>(LogItem::DateTime) | stattic_cast<int>(LogItem::LoggerName) |
				static_cast<int>(LogItem::LogLevel)) : m_level(level), m_name(name), m_loggableItem(loggableItems)
			{				
			}

			~CLogger()
			{
			}

			void AddOutputStream(tostream& os, bool own, LogLevel level)
			{
				AddOutputStream(&os, own, level);
			}

			void AddOutputStream(tostream& os, bool own)
			{
				AddOutputStream(os, own, m_level);
			}

			void AddOutputStream(tostream* os, bool own)
			{
				AddOutputStream(os, own, m_level);
			}

			void AddOutputStream(tostream* os, bool own, LogLevel level)
			{
				StreamInfo si(os, own, level);
				m_outputStreams.push_back(si);
			}
			
			void ClearOutputStreams()
			{
				for(vector<StreamInfo>::iterator iter = m_outputStreams.begin(); iter < m_outputStreams.end(); iter++)
				{
					if(iter->owned) delete iter->pStream;
				}

				m_outputStreams.clear();
			}

			void Log(LogLevel level, LPCTSTR file, INT line, LPCTSTR func, LPCTSTR text)
			{
				m_threadProtect.Lock();

				for(vector<StreamInfo>::iterator iter = m_outputStreams.begin(); iter < m_outputStreams.end(); iter++)
				{
					if(level < iter->level)
					{
						continue;
					}

					bool written = false;
					tostream * pStream = iter->pStream;
				
					if(m_loggableItem & static_cast<int>(LogItem::DateTime))
						written = write_datetime(written, pStream);
				
					if(m_loggableItem & static_cast<int>(LogItem::ThreadId))
						written = write<int>(GetCurrentThreadId(), written, pStream);

					if(m_loggableItem & static_cast<int>(LogItem::LoggerName))
						written = write<LPCTSTR>(m_name.c_str(), written, pStream);

					if(m_loggableItem & static_cast<int>(LogItem::LogLevel))
					{
						TCHAR strLevel[4];
						loglevel_toString(level, strLevel);
						written = write<LPCTSTR>(strLevel, written, pStream);
					}

					if(m_loggableItem & static_cast<int>(LogItem::Function))
						written = write<LPCTSTR>(func, written, pStream);

					if(m_loggableItem & static_cast<int>(LogItem::Filename))
						written = write<LPCTSTR>(file, written, pStream);

					if(m_loggableItem & static_cast<int>(LogItem::LineNumber))
						written = write<int>(line, written, pStream);
								
					written = write<LPCTSTR>(text, written, pStream);

					if(written)
					{
						(*pStream) << endl;
						pStream->flush();
					}
				}

				m_threadProtect.Unlock();
			}

		private:
			int m_loggableItem;
			LogLevel m_level;
			tstring m_name; 
			vector<StreamInfo> m_outputStreams;
			ThreadingProtection m_threadProtect;
	
			template <class T> inline bool write(T data, bool written, wostream* strm)
			{
				if(written == true)
				{
					(*strm) << _T(" ");
				}

				(*strm) << data;
				return true;
			}

			inline bool write_datetime(bool written, wostream* strm)
			{
				if(written == true)
				{
					(*strm) << _T(" ");
				}

				time_t szClock;
				tm newTime;

				time( &szClock );
				localtime_s(&newTime, &szClock);
			
				TCHAR strDate[10] = { _T('') };
				TCHAR strTime[10] = { _T('') };

				_tstrdate_s(strDate, 10);
				_tstrtime_s(strTime, 10);

				(*strm) << strDate << _T(" ") << strTime;
		
				return true;
			}

			void loglevel_toString(LogLevel level, LPTSTR strLevel)
			{
				switch (level)
				{
				case LogLevel::Error:
					_tcscpy_s(strLevel, 4, _T("ERR"));
					break;

				case LogLevel::Warn:
					_tcscpy_s(strLevel, 4, _T("WRN"));
					break;

				case LogLevel::Info:
					_tcscpy_s(strLevel, 4, _T("INF"));
					break;

				case LogLevel::Debug:
					_tcscpy_s(strLevel, 4, _T("DBG"));
					break;
				}
			}
		};
	}
}

Пример работы

Простой логгер для C++
Простой логгер для C++

Автор: Renzo

Источник


  1. Святой Барсук:

    Есть опечатки в листингах (особенно если собирать версию без юникода), но в целом работает. Спасибо.
    PS На плюсах не пишу много, срочно понадобился логгер простой, выручила статья. <3

    • Вячеслав:

      Не собирается сырок. Можно Вас попросить опубликовать где-нибудь Ваши исходники ?

    • Вячеслав:

      Замучался адаптировать программу. Вас можно попросить выложить куда-нибудь запущенные сырки ?

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js