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

Автор: Renzo

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