Логування повідомлень з Easylogging + +

    
Система логування — незамінний інструмент для протоколювання роботи додатків. Для тих, хто не хоче реалізовувати його самостійно, на C + + вже існує незліченну кількість готових бібліотек (Log4cplus, Apache log4cxx, Boost.Log і тд.), Проте Easylogging + + відрізняється простотою використання і компактністю, не вимагає сторонніх бібліотек або інсталяції. Весь її код міститься в одному єдиному заголовному файлі, який просто необхідно включити в код програми.
 
Дана стаття пропонує короткий огляд функціоналу Easylogging + + і приклади використання цієї бібліотеки.
 
Відразу почнемо з короткого прикладу, який повинен продемонструвати, наскільки просто можна почати користуватися Easylogging + +:
 
 
#include "easylogging++.h"

_INITIALIZE_EASYLOGGINGPP

int main(int argv, char* argc[]) 
{
   LOG(INFO) << "Привет Habrahabr";
   return 0;
}

Бібліотека підключається одним заголовком, ніяких lib-ів не потрібно.
У наведеному вище короткому прикладі коментаря заслуговує виклик макросу _INITIALIZE_EASYLOGGINGPP. Він потрібен для ініціалізації деяких статичних змінних і установки обробника для креш. Виклик повинен робитися один і тільки один раз, до початку використання бібліотеки.
 
 

Довідка:

 
     
  • Сайт бібліотеки: easylogging.org /
  •  
  • Ліцензія: MIT
  •  
  • Мова: C + + 11
  •  
  • Залежить від: —
  •  
  • Платформи: Windows, Mac OSX, Debian (Ubuntu, Mint), Fedora
  •  
  • Компілятори: GCC 4.7 +, Visual C + + 11.0 +, Intel C + + 13.0 +, MinGW, Cygwin
  •  
 

Навіщо:

Підкуповує простота і інтуїтивність бібліотеки. Нам не вдалося знайти інших реалізацій, що вміщується в єдиному заголовному файлі (який легко використовувати з проекту в проект) і дають вельми повний набір можливостей:
 
 
     
  • Кросплатформеність
  •  
  • Нить
  •  
  • Налаштування формату видачі
  •  
  • Видача custom типів даних (є готова реалізації для STL контейнерів)
  •  
  • Перемикання файлів журналу (rolling)
  •  
  • Умовна і періодична видача
  •  
  • Перевірки та обробка збоїв програми
  •  
  • Розрахунок часу виконання методів
  •  
 

Налаштування формату

Незважаючи на те, що на нашу думку головною перевагою бібліотеки є її автономність, компактність і простота, іноді все ж виникає потреба змінити щось в її поведінці. Наприклад, за замовчуванням формат видачі виглядає так:
 
11/06/2014 11:23:29,218 INFO  [default] Simple info message
11/06/2014 11:23:29,218 ERROR [default] Simple error message
11/06/2014 11:23:29,218 WARN  [default] This is just a warning

Для початку Easylogging надає розробнику можливість налаштувати набір виведених полів, а також їх порядок.
Приклад нижче демонструє, як можна поставити тип повідомлення в початок, потім дату у зміненому форматі, далі сигнатуру методу, з якого був зроблений виклик і саме повідомлення:
 Налаштування полів
el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Format, "%level %datetime{%H:%m:%s} (%func): %msg");
LOG(INFO) << "После изменения формата";

Результат на екрані буде виглядати так:
 
INFO  11:33:58 (int main(int, char**)): После изменения формата

 
Крім стандартних речей в описі формату, таких як тип повідомлення, дата або ім'я файлу (cpp), можна використовувати і власні макроси для видачі в журнал додаткової інформації. Наприклад, так:
 Свої поля
const char* getMode(void) 
{
	switch(mode)
	{
		case 1: 
			return "Service"; 
		default: 
			return "App"; 
	}
	return ""; // just in case
}
int main(int argv, char* argc[]) 
{
	el::Helpers::installCustomFormatSpecifier(el::CustomFormatSpecifier("%mode", getMode));
	el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Format, "%level %mode: %msg");
	LOG(INFO) << "После изменения формата";
	return 0;
}

 
Ще є цілий ряд прапорів змінюють різні аспекти поведінки Easylogging. За замовчуванням, наприклад, вся видача дублюється в stdout. Крім того, що це легко зовсім відключити, можемо, навпаки, зробити все більш наочним, встановивши прапор для активації кольоровий видачі:
 Установка прапорів
int main(int argv, char* argc[]) 
{
	/* Включаем выдачу на экран (она и так включена) */
	el::Loggers::reconfigureAllLoggers(el::ConfigurationType::ToStandardOutput, "true");
	/* Включаем флаг цветной печати */
	el::Loggers::addFlag(el::LoggingFlag::ColoredTerminalOutput);
	LOG(INFO) << "Обычное сообщение";
	LOG(ERROR) << "Ошибка должна стать цветной";
	LOG(WARNING) << "А предупреждение желтым";
	return 0;
}

Результат на екрані буде виглядати так:
  
 
Будь-яку настройку можна змінити не тільки з коду: додатково бібліотека підтримує файли конфігурації у власному форматі (звичайний конф з табами), які можна завантажувати під час запуску або роботи додатків. Також бібліотека вміє читати параметри з командного рядка: для цього в main потрібно помістити відповідний виклик:
 Читання командного рядка
int main(int argv, char* argc[]) 
{
   _START_EASYLOGGINGPP(argc, argv);
}

 
 

Користувальницькі типи

Бібліотека дозволяє виводити в журнал вміст об'єктів довільного типу. Зробити це можна двома способами:
 
     
  • Успадковувати свій об'єкт від якогось el :: Loggable і реалізувати відповідний віртуальний метод
  •  
  • Реалізувати статичний метод (оператор) для відправки свого об'єкта в журнал
  •  
Другий варіант нам видається більш зручним, тому що не треба «бруднити» свої класи незрозумілими предками і, крім того, якщо ви вирішите прибрати логирование, не доведеться чистити всі класи. Покажемо, як виглядатиме реалізація другим способом:
 Користувальницькі типи
/* Просто такой класс */
class Dummy
{
public:
    Dummy(int i, std::string s) : m_Int(i), m_String(s){}
    int getInt() const{return m_Int;}    
	std::string const& getString() const {return m_String;}
private:
    int m_Int;
    std::string m_String;
};

/* Используем макрос из библиотеки, который объявит за нас operator<< */
inline MAKE_LOGGABLE(Dummy, obj, os) 
{
	/* Делаем выдачу нужных атрибутов в поток журнала */
    os << "{i:"<<obj.getInt()<<", s:"<<obj.getString()<<"}";
    return os;
}

int main(void) 
{
    Dummy object(1, "Habrahabr");
    LOG(INFO) << "Dummy: " << object;
    return 0;
}
/*
Результат:
	11:03:27 INFO  Dummy: {i:1, s:Habrahabr}
*/

 
 

Файли журналу

За замовчуванням файл журналу буде розташований в робочій директорії програми та матиме шлях… / logs / myeasylog.log.
Змінити ім'я файлу журналу або його розташування можна в будь-який момент. Це може бути корисно, якщо потрібно, наприклад, перемикатися на новий файл кожну добу. Наведемо приклад ручного перемикання файлу журналу:
 Ручне перемикання
int main(void)
{
    LOG(INFO) << "Попадет в файл журнала по умолчанию";
    el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Filename, "logs/20140205.log");
    LOG(INFO) << "Попадет в другой файл журнала";
    return 0;
}

 
Після виконання цього в папці logs виявиться два файли: myeasylog.log і 20140205.log. Кожне повідомлення опиниться в своєму файлі. Неважко бачити, що аналогічно можна в «ручному» режимі виконувати перемикання журналів за заданим алгоритмом (наприклад зробити добові журнали або виділяти певне число записів на кожен файл)
 
Easylogging + + вміє перемикати журнали автоматично, але виключно на підставі розміру їх файлу. Установки дозволяють задати порогове значення на розмір файлу, після якого поточний журнал буде обнулено. Перед настанням цієї події вам буде надана можливість скопіювати попередній файл журналу. Нижче наводиться приклад, де ми встановлюємо 1Кб ліміт для розміру файлів і перед перемиканням на новий журнал зберігаємо резервну копію старого:
 Прихований текст
/* Счетчик номеров журнала. Если журналы заполняются плавно 
 * то можно брать timestamp.*/
int log_sequence = 1;

/* Callback будет вызван когда старый файл журнала fname заполнен */
void LogsRollout(const char* fname, size_t fsize)
{	
	/* Допишем к имени файла текущий номер последовательности (или время) */
	string fileName = fname;
	size_t position = fileName.find(".");
	string extractName = (string::npos == position)? fileName : fileName.substr(0, position);
	extractName = extractName + to_string(log_sequence++) + ".log";
	
	/* Старый фал журнала уже закрыт, переименуем его */
	int status = rename(fname, extractName.c_str());	
	if(status)
	{
		/* Не смогли переименовать */
	}
}

int main(void) 
{
	/* Ставим флаг для переключения журналов по размеру файла */
	el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck);
	/* Ставим проговое значение на размер файла в 1Кб */
	el::Loggers::reconfigureAllLoggers(el::ConfigurationType::MaxLogFileSize, "1024");
	/* Передаем callback на переключение файлов */
	el::Helpers::installPreRollOutCallback(LogsRollout);
	/* Добавляем много сообщений */
	for(int i=0; i<1024; i++)
	{
		LOG(INFO) << "Message #"<< i+1;	
	}
    return 0;
}

 
 

Умовна і періодична видача

У бібліотеці оголошений макрос LOG_IF для видачі повідомлення в журнал тільки у разі виконання зазначеної умови:
 Прихований текст
LOG_IF(true, INFO) << "Всегда выводится";
LOG_IF(false, WARNING) << "Никогда не выводится";

 
Періодична видача також може бути корисна в ряді випадків. Такий спосіб означає, що в журнал буде потрапляти не кожне повідомлення, а, наприклад, кожне 10е або 100е. При цьому реалізовано дві можливості:
 
     
  • LOG_EVERY_N (n, LEVEL) — виводить кожне n-е повідомлення
  •  
  • LOG_AFTER_N (n, LEVEL) — виводить повідомлення тільки після n спрацьовувань
  •  
 

Перевірки та обробка збоїв програми

У бібліотеці оголошений цілий ряд макрос для видачі повідомлення в журнал та аварійного завершення тільки в разі не виконання деякого критичного умови: CHECK, CHECK_EQ, CHECK_NE, CHECK_NOTNULL і т.д. Ці макроси дуже нагадують assert, тільки з видачею в журнал.
Користуватися ними досить зручно:
 Прихований текст
CHECK(true) << "Истина в вине";
CHECK_LE(2, 1) << "2 < 1 ???";
CHECK_STREQ(argv[3], "1") << "Передан аргумент отличный от 1";

Ось так падає при невиконанні умови:
 
07:12:51,375 FATAL Check failed: [2 <= 1] 2 < 1 ???
07:12:51,375 WARN Aborting application. Reason: Fatal log at [/home/borg/Temp/App/main.cpp:34]
Aborted

 
Якщо не змінювати налаштування за замовчуванням, то в разі не виконання умови, програма буде перервана, а повідомлення з'явиться в журналі з рівнем FATAL.
 
Додатково, бібліотека за замовчуванням обробляє збої програми. Це поведінка, звичайно, можна відключити, але іноді може бути зручно отримувати логи на ряд сигналів, наприклад: SIGFPE, SIGILL, SIGSEGV і т.д. Для gcc можна також видавати в журнал call stack.
 Прихований текст
#include "easylogging++.h"
_INITIALIZE_EASYLOGGINGPP

void CustomCrashHandler(int signal) 
{
    LOG(ERROR) << "Всё сломалось!!"; 
	/* Вызываем на помошь библиотеку */    
    el::Helpers::crashAbort(signal);
}
int main(int argv, char* argc[]) 
{
	/* Установим свой обработчик */
    el::Helpers::setCrashHandler(CustomCrashHandler);
	/* Деление на 0 */
    int a = 0;
	a = 1/a; 

    return 0;
}

Ось так падає:
 
17:46:53,074 ERROR Всё сломалось!!
Aborted

 
 

Висновки

Бібліотека Easylogging + + представляє більш менш повний набір функціоналу, який можна було б очікувати побачити в такому продукті.
До безперечних плюсів слід віднести: її компактність, простоту роботи і кроссплатформенность.
До недоліків ми б віднесли лише вимога до використання C + + 11, хоча, ймовірно, автори могли б обійтися і «звичайним» C + +.
    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.