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