29 мар. 2012 г.

Простая система логирования на C++

    Система логирования - нужный и полезный инструмент для протоколирования работы любой программы. Она значительно экономит время, проведенное под отладкой и вылавливанием различных багов.
Существует много готовых библиотек (log4cplus, Apache log4cxx и тд.), однако в ряде случаев гораздо удобней и полезней иметь свою реализацию, которую можно в дальнейшем модифицировать под конкретную задачу.
И так, сначала определимся с требованиями к ней. Это удобство использования, эффективность применения, возможность расширения и дальнейших модификаций, типобезопасность и, по возможности, кросс платформеннсть. Также она будет поддерживать несколько уровней детализации сообщений (info, error, warning, debug ...). 
Для вывода форматированного текста мы воспользуемся функционалом  std::ostringstream, в нашем классе лога это переменная “m_oss”, которая будет накапливать в себе все сообщения. Функция член класса GetMsg() будет обеспечивать доступ к этой переменной.
Допустим мы хотим, чтобы сообщения могли выводиться как в консоль, так и в файл лога. Для этого заведем два класса: один будет отвечать за сбор сообщений, а второй, куда и как именно они будут выводиться.
Листинг шаблонного класса лога:
// уровни протоколирования
enum LogLevel_t { logError, logWarning, logInfo, logDebug,
                  logDebug1, logDebug2, logDebug3 };

template < typename T >
class Logger {
public:
    Logger( void );
    virtual ~Logger( void );

    std::ostringstream& GetMsg(LogLevel_t level = logInfo);
    // устанавливает уровень детализации
    static LogLevel_t& ReportingLevel( void );
 
    static const char* LevelToString(LogLevel_t level);
    static LogLevel_t LevelFromString(const char* ch_level);

protected:
    std::ostringstream m_oss;
private:
    Logger(const Logger&);
    Logger& operator = (const Logger&);
};

Класс, отвечающий за реализацию вывода сообщений и их параметры:
class OutStream {
public:
    // инициализирует(пересоздавая) файл лога
    static bool InitLogFile(const char* nameFileLog);
    // перенаправление вывода сообщений в файл
    static bool UseFile( void );
    // перенаправление вывода сообщений в консоль
    static bool UseStderr( void );
    // надо ли выводить время события в лог
    static bool& IsOutputTime( void );
};
Объединим функционал двух классов:
class Log : public Logger < OutStream > { };
Теперь можем использовать класс лога, как показано ниже:
static const int lost[] = {4, 8, 15, 16, 23, 42};

Log().GetMsg(logWarning)<<"\n\t >: "<< lost[0] <<" "<< lost[1] <<" "<< 

lost[2] <<" "<< lost[3] <<" "<< lost[4] <<" "<< lost[5] << " end.";
Результат:
> WARNING(03:27:54.000) : 
  >: 4 8 15 16 23 42 end.
Выполнение этого кода будет создавать экземпляр лога с logWarning уровнем детализации, далее будет получен объект std::sstringstream с накопленными данными, а затем собрана готовая строка, которую можно вывести куда угодно одним лишь вызовом fprintf().
Для установки уровня детализации достаточно написать:
Log::ReportingLevel() = logDebug2;
Для удобства завернем все это в макросы:
#define TO_LOG(level) \
    if (level > logDebug3); \
    else if(level  > Log::ReportingLevel()); \
    else Log().GetMsg(level)

#define LOG_FILE_INIT(x)      OutStream::InitLogFile(x)
#define LOG_IS_OUTPUT_TIME(x) OutStream::IsOutputTime() = x;
#define LOG_USE_FILE          OutStream::UseFile();
#define LOG_USE_STDERR        OutStream::UseStderr();
Теперь мы можем писать так:
// устанавливаем уровень выводимых сообщений
    Log::ReportingLevel() = Log::LevelFromString("DEBUG2");
    LOG_FILE_INIT("MyLog.log");

    // разрешаем выводить время сообщения
    LOG_IS_OUTPUT_TIME(true)
    LOG_DEBUG_3 << "Initialize the log file!";
    LOG_IS_OUTPUT_TIME(false)
 
    // выводим на консоль через stderr
    LOG_USE_STDERR
    LOG_WARNING << "Some message";
    // снова выводим сообщения в файл лога
    LOG_USE_FILE
    LOG_INFO << "Message out in the file";
    И так, у нас получилось что-то вроде ран-тайм полиморфизма или стейт машины для системы логирования. При желании можно сделать выводимые в консоль сообщения различных цветов в зависимости от их уровней.
Если систему логирования предполагается использовать одновременно из нескольких потоков, то можно добавить в класс, реализующий вывод сообщений "C_OutSream", какой-нибудь объект синхронизации, например мьютекс.
static std::mutex g_mutex;

class C_OutStream : public OutStream {
public:
    static void OutputFile(const char* msg);
    static void OutputStdErr(const char* msg);
};

void C_OutStream::OutputFile(const char* msg)
{
    g_mutex.lock();

    FILE* pStream = fopen(s_logFileName.c_str(), "a");
 
    if (!pStream)
      return;
 
    fprintf(pStream, "%s", msg);
    fflush(pStream);
    fclose(pStream);

    g_mutex.unlock();
}
Source code: Logger_VS10.rar

Комментариев нет:

Отправить комментарий