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

от автора

Недавно я искал удобный, простой и в то же время функциональный логгер для своих приложений. В принципе, для этого уже существует много фреймворков. Но я искал ни сколько доступный для копипастинга код, сколько сами подходы к ведению лога. Спустя некоторое время я наткнулся на 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. По умолчанию, это настроено так:

    Таким образом, если вы, например, хотите, чтобы в лог выводились только дата и время, создание экземпляра приняло бы следующий вид:
    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('\0') }; 				TCHAR strTime[10] = { _T('\0') };  				_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; 				} 			} 		}; 	} } 

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


ссылка на оригинал статьи http://habrahabr.ru/post/185222/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *