В сети огромное количество площадок формата Q&A где задаются вопросы из разряда:
- Предложите С++ логгер? (C++ logging framework suggestions)
- Какой наиболее эффективный потоко-безопасный С++ логгер? (What is the most efficient thread-safe C++ logger)
- Библиотека логирования для игр (Logging library for c games)
- Асинхронный потоко-безопасный С++ логгер? (Asynchronous thread-safe logging in C++)
Люди делятся своим опытом и знаниями, но формат таких площадок позволяет лишь показать личные предпочтения отвечающего. К примеру, одним из самых производительных логгеров чаще всего называют Pantheios, который даже по тестам производителя тратит больше 100 секунд на запись 1M строк лога, на современном железе это около 30 секунд, быстро ли это?
В этой статье я сравню наиболее известные и заслуженные логгеры последних лет и несколько относительно молодых логгеров по более чем 25 критериям.
Мотивация
Почти в каждом известном мне проекте рано или поздно появлялось логирование и инженеры спрашивали себя «а как решить эту задачу?», кто-то рылся на площадках Q&A и получал ответы «Мне подошел логгер X, вроде полет нормальный» (1), кто-то писал свой логгер (2), а кто-то запасался терпением и штудировал целый пучок логгеров на предмет своих интересов и спустя неделю другую делал свой выбор (3).
Эта статья для всех 3х групп:
- Для первой группы эта статья даст более обширное сравнение логгеров, чем рекомендация «логгер Х для меня лучший»
- Для второй группы статья даст представление о текущем уровне технологий и возможно склонит чашу весов в сторону «проще использовать готовый» или «ого, работы много, но я смогу лучше» и кто знает, может через год другой мы увидим прорыв в этой области.
- Для третьей и самой педантичной группы я надеюсь, эта статья сохранит как минимум одну из 2х недель изысканий, в масштабах всей индустрии это может быть внушительное количество человеко-часов
N.B. Статья достаточно объемная, поэтому если решитесь на чтение, пожалуйста, запаситесь терпением!
логгеры и их основные параметры
Выбор логгеров для сравнения дело хлопотное и не простое, в любом случае возникнут вопросы «А почему логгер Х не был рассмотрен?» Что тут можно сказать, логика была проста – взять 3 известных логгера и 4 сравнительно юных и «голодных».
Но даже сравнение этих 7 кандидатов заняло более 2х недель выкуривания доков, issues, чтения форумов, написания тестов и сбора результатов.
Так или иначе, если какой-то очень важный логгер был пропущен (boost к примеру) – статью можно обновить. В обзор попали:
Общие характеристики выбранных логгеров
Лиц. | Языки | Обнов. | Платф. | Комп. | |
---|---|---|---|---|---|
Pantheios | BSD | C++ | 2010 | Windows, *nix, OS-X | VC++, GCC, Intel, Borland, Comeau,Digital Mars,Metrowerks |
Glog | Неизвестна | С++ | 2016 | Windows, *nix, QNX | VC++, GCC, clang, intel |
log4cpp | LGPL | С++ | 2016 | Windows, *nix, Solaris | VC++, GCC, Sun CC, OpenVMS |
P7 | LGPL | С++, С, C#, Python | 2016 | Windows, *nix | VC++, GCC, clang |
G3log | Public Domain | C++11 | 2016 | Windows, *nix | VC++, GCC, clang |
Spdlog | MIT | C++11 | 2016 | Windows, Linux, Solaris, OS-X, Android | VC++, GCC, Clang |
Easylogging | MIT | C++11 | 2016 | Windows, Linux, Solaris, OS-X, Android, RaspberryPi | VC++, GCC, Clang, Intel |
Документация и зависимости
Трудно оспаривать важность документации для сложных проектов, современные логгеры простыми проектами назвать можно с большой натяжкой и наличие хорошей документации порой существенно ускоряет внедрение и исправление ошибок:
Документация | Зависимости | |
---|---|---|
Pantheios | Полная (API + использование) | STLSoft |
Glog | Рудиментарная, почти отсутствует | Google gflags, слабая зависимость (1) |
log4cpp | Генерируемая (Doxygen) (API только) | Boost, слабая зависимость (1) |
P7 | Полная (API + использование) | Нет |
G3log | Базовая (общие методы использования) | Нет |
Spdlog | Базовая (общие методы использования) | Нет, только заголовочный файл (2) |
Easylogging | Базовая (общие методы использования) | Нет, только заголовочный файл (2) |
- Слабая зависимость – часть кода зависит от сторонних решений, но не препятствует компиляции, а лишь частично ограничивает функционал.
- Только заголовочный файл – библиотека распространяется в виде одного или нескольких заголовочных файлов, которые необходимо включать в каждый исходный файл Вашей программы, в случае Easylogging существенно снижается скорость компиляции. Но из этой ситуации есть выход – компилировать эти проекты в виде библиотеки и подключать уже ее, правда это потребует дополнительного времени на создание проекта.
Тип логгера, контроль потребления памяти и потокобезопасность
На данный момент широко распространены 2 подхода в логировании:
- Синхронный – вызов Log(..) функции и запись в файл происходят синхронно из одного потока
- Асинхронный – вызов Log(..) функции лишь обновляет очередь сообщений, а другой поток занимается записью, таким образом, уменьшая время вызова Log(..) функции из пользовательского потока, и как следствие достигается максимальная производительность пользовательского потока и минимизируются задержки на вызов Log(..) функции.
Правда есть нюансы понимания асинхронности разными производителями библиотек логирования.
- Тип №1: Одни считают, что вызов Log(..) функции должен быть атомарным и таким образом порядок лог сообщений в файле будет последовательным во времени 00:00 → 00:01 → 00:02 и тд.
- Тип №2: Другие считают, что в целях достижения максимальной производительности можно пожертвовать атомарностью вызова Log(..) функции и смириться с тем, что лог сообщения в файле будут перемежающимися, например вот так 00:00 –> 00:05 -> 00:01.
Лично я придерживаюсь точки зрения первой группы на асинхронное логирование, так как анализ перемешанных логов приятным назвать нельзя, особенно если это большой лог файл и перемешаны не единичные логи а группы логов по несколько сотен или тысяч элементов.
Другим важным аспектом асинхронного логирования является контроль за выделением памяти, так как, чтоб ваш логгер был асинхронен — вы должны сохранять данные в буфера, а писать уже из другого потока. И вот тут кроется важная мелочь — какой размер буферов будет оптимален и может ли пользователь влиять на этот параметр? Вопрос совсем не праздный, так как некоторые из протестированных логгеров выделяли сотни мегабайт под их нужды.
Тип | Контроль памяти | Потоко-безопасность | |
---|---|---|---|
Pantheios | Синхонный | нет | да |
Glog | Синхонный | нет | да |
log4cpp | Синхонный | нет | да |
P7 | Асинхронный, тип 1 (2) | точное (с шагом 1Kb) | да |
G3log | Асинхронный, тип 2 (3) | нет (5) | да |
Spdlog | Асинхронный, тип 2 (3) | частичное (6) | да |
Easylogging | Синхонный (1) | нет | нет (4) |
- Асинхронный режим находится в экспериментальном состоянии
- Временные метки и сообщения не перемешаны
- Временные метки и сообщения могут быть перемешаны
- По умолчанию не доступно, необходимо активировать макросом ELPP_THREAD_SAFE
- Неконтролируемое выделение памяти, при высоких нагрузках может выделять сотни мегабайт, комментарий автора: kjellkod.wordpress.com/2014/08/16/presenting-g3log-the-next-version-of-the-next-generation-of-loggers
In the case of g2log or g3log a std::queue is used, internally that is a std::deque. It’s unbounded but much more memory tolerant than a std::vector. Internally the queue is wrapped inside the shared_queue.hpp.
- Можно задавать длину очереди в элементах, размер элемента в начальном виде – 88 байт + текстовое сообщение (30-160 байт). Автор рекомендует задавать размер очереди в 1 миллион сообщений для оптимальной производительности, что выливается в расходы памяти от 120 мегабайт до 250 мегабайт только на библиотеку логирования.
Обработка сбоев процесса
Правильная обработка сбоев процесса (crash handling) в первую очередь важна для асинхронных логгеров, т.к. часть данных хранится в буферах и если их вовремя не сохранить, то возможно самые ценные данные прямо перед сбоем будут потеряны.
Распространены 3 подхода в перехвате падений:
- Автоматический, библиотека сама настраивает все векторы и сделает все сама, большим минусом такого решения является скудность перехватываемых сигналов, а так же помехи для приложения, которое возможно хотело бы само обрабатывать падение в целях сохранения crash dump файла или сброса буферов.
- Ручной – библиотека предоставляет примитивы для перехвата падений и сброса буферов, а приложение уже само решает, когда и как установить перехватчик и что делать в случае падения.
- Пусть падает, дело житейское
Pantheios | Нет |
---|---|
Glog | автоматический, только под Linux (1) |
log4cpp | Нет |
P7 | ручной (2) |
G3log | автоматический, только под Linux (3) |
Spdlog | Нет (4) |
Easylogging | автоматический, только под Linux (5) |
- Перехватывается следующие сигналы: SIGSEGV, SIGILL, SIGFPE, SIGABRT, SIGBUS, SIGTERM.
- Перехватывается следующие сигналы: SIGSEGV, SIGILL, SIGFPE, SIGINT, SIGABRT, SIGBUS, SIGTERM, SIGBUS, PureVirtualCall, VectoredException, Newhandler, InvalidParameterHandler, status_access_, exception_array_bounds_exceeded, exception_datatype_misalignment, exception_flt_divide_by_zero, exception_flt_stack_check, exception_illegal_instruction, exception_int_divide_by_zero, exception_noncontinuable_exception, exception_priv_instruction, exception_stack_overflow
- Перехватывается следующие сигналы: SIGSEGV, SIGILL, SIGFPE, SIGABRT, SIGBUS, SIGTERM, Div by zero, illegal printf, out of bounds, access violation, std::future_error
- github.com/gabime/spdlog/issues/55 — Дефект был «закрыт» в 2015, т.е. работ не предвидится по этому направлению.
- Перехватывается следующие сигналы: SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGINT
Стиль логирования и вывод (sink)
Основная масса библиотек поддерживает 2 хорошо зарекомендовавших себя стиля логирования:
- Функции со списком переменных аргументов (prinf стиль)
- Перегрузка оператора “<<”
Стиль | Вывод (Sink) | |
---|---|---|
Pantheios | Template + перегруженные функции F(A), F(A,A), F(A,A,A), … F(A, <-64->, A) Printf |
File, syslog, console, speech, ACE, COMerror, WinEventLog |
Glog | Log() << Message | File, syslog, console |
log4cpp | Printf, Log() << Message | File, syslog, console, NT log, IDS/A, OsStream, StringQueue, Win32Debug |
P7 | Printf | File, network (собств. протокол и сервер(2)), null |
G3log | Printf, Log() << Message | File (3) |
Spdlog | Printf | File, syslog, console |
Easylogging | Printf (1), Log() << Message | File, syslog, console |
- В случае использования «Printf» генерировал exception, вероятно это временная проблема.
- Свой сервер, используется для достижения максимальной производительности. Сервер бесплатен, но к сожалению поддерживает только Windows, судя по всему основан на Qt, запрос об поддержке Linux был направлен автору. Скорость отправки логов по сети в тестовой конфигурации была около 3.5 миллионов в секунду при загрузке CPU – 13%. Подробно тесты производительности обсуждаются в следующих главах.
- В официальную поставку не входит Sink с поддержкой ротации файлов и консоль, но эти расширения можно скачать github.com/KjellKod/g3sinks
Инициализация логгера
Инициализация или передача параметров достаточно важный пункт т.к. добавляет гибкости логгеру и избавляет от необходимости перекомпиляции, если Вы решили изменить уровень логирования к примеру.
Pantheios | Ручная (только в коде) |
---|---|
Glog | Командная строка, ручная, переменные среды окружения |
log4cpp | Конфигурационный файл (1), ручная |
P7 | Командная строка (2), ручная |
G3log | Ручная (только в коде) |
Spdlog | Ручная (только в коде) |
Easylogging | Конфигурационный файл, командная строка, ручная |
- Подробная и хорошо организованная настройка параметров логгера, пожалуй, самая развернутая из всех.
- Во всех остальных логгерах для того чтоб параметры командной строки были обработаны – вы должны передать их в логгер руками из int main(int argc, char* argv[]) функции. В данном логгере эти параметры могут быть перехвачены автоматически из любой части программы/модуля (dll,so).
Настройка фильтрации
Самая распространенная методика фильтрации – по уровням логирования, скажем если фильтр установлен на ERROR уровень — то все что меньше ERROR (TRACE, DEBUG, INFO, WARNING …) в лог попадать не будет. Этот метод очень удобен для отсеивания большого количества ненужной в данный момент информации и сохранения CPU и места на диске.
Pantheios | Нет (1) |
---|---|
Glog | Командная строка, ручная, переменные среды окружения |
log4cpp | Конфигурационный файл (4), ручная |
P7 | Командная строка, удаленно по сети в режиме реального времени (2) (3), ручная |
G3log | Нет (5) |
Spdlog | Ручная |
Easylogging | Ручная (6) |
- Для организации фильтрации нужно разработать свой FrontEnd
- Поддерживается только если данные высылаются по сети, в случае записи в локальный файл сервер не имеет доступа к Verbosity level
- В дополнение к глобальному уровню можно устанавливать уровни для каждого модуля.
- Иерархические логирование и установка уровней индивидуально для каждого логгера
- По умолнчанию отключена, включается макросом G3_DYNAMIC_LOGGING, после этого можно в ручном режиме задать уровень на все логгеры. Существенно снижает производительность.
- Поддержка заявлена производителем, но заставить ее работать не удалось, во время использования сложилось впечатление, что функция находится в стадии разработки или заброшена.
Поддержка юникода
Эта часть тестирования была одной из самых грустных, в 2016 году поддержка юникода в таких известных библиотеках все еще находится на уровне «not officially».
Библиотека нужна для того что сохранить важные данные приложения (фамилия пользователя, путь файла, имя домена), а большинство из существующих просто не позволят Вам это сделать если данные не укладываются в тривиальный char.
Pantheios | Utf-16 (1)(4), Utf-8 |
---|---|
Glog | Нет |
log4cpp | Нет |
P7 | Windows — Utf-16, *nix — Utf-8 |
G3log | Нет |
Spdlog | Нет |
Easylogging | Windows Utf-16 (2), Utf-8 (3) |
- Почти идеально, за исключением того что итоговый лог файл не имеет маркера юникода и кодировку в программе просмотра нужно будет выбирать самому.
- Поддержка заявлена, но не реализована, символы юникода в лог файл не попадают.
- Макрос START_EASYLOGGINGPP не поддерживает юникод
- В одном сообщении нельзя совместить ANSI строку скажем с UTF-16
Доступ к логгеру
В современных библиотеках вопрос «кто владеет логгером» остается за кадром, чаще всего можно написать LOG(ERROR) << “My message” и библиотека позаботиться обо всем сама. Такая простота достигается с помощью глобальных переменных. Этичность использования глобальных переменных я оставлю за кадром, все таки это особый случай, но простота использования глобальной переменной в случае простого приложения оборачивается против разработчика сложного приложения состоящего из многих динамических или статических модулей.
Другой вариант получения доступа к логгеру это самостоятельно создать объект и контролировать его жизненный цикл.
И последний вариант – гибридный, объекты логгера создаются в ручном режиме, а затем используется глобальные переменные (registry) или разделяемая память, общая для всего процесса включая динамические модули.
Pantheios | Глобальные переменные, автоматическая инициализация |
---|---|
Glog | Глобальные переменные, автоматическая инициализация |
log4cpp | Глобальные переменные, автоматическая и ручная инициализация |
P7 | Общая память, ручная инициализация |
G3log | Глобальные переменные, автоматическая и ручная инициализация |
Spdlog | Глобальные переменные, ручная инициализация |
Easylogging | Глобальные переменные, автоматическая инициализация |
Ротация файлов
Pantheios | Нет |
---|---|
Glog | Размер |
log4cpp | Размер (2) |
P7 | Время, размер (1) (2) |
G3log | Размер, по умолчанию не доступен (1) |
Spdlog | Размер, время(дневной) (1) |
Easylogging | Размер |
- Каждый файл в названии содержит дату и время
- Поддерживается опция «макс. кол-во файлов» позволяющая хранить только N последних файлов
Точность времени
Многие из рассматриваемых в данной статье логгеров разрабатывались с прицелом на высокую производительность, с потенциалом в миллионы сообщений в секунду. Но помимо высоких скоростей нужны точные временные метки высокого разрешения, т.к. если у вас в лог файле есть пара десятков или даже сотен сообщений с одинаковой временной меткой – это означает, что часть информации о времени исполнения уже утеряна.
Pantheios | Windows: 10ms, custom back-end может помочь увеличить точность Linux: теор. минимальное значение 1ns, зависит от аппаратной части |
---|---|
Glog | Windows: 10ms Linux: теор. минимальное значение 1ns, зависит от аппаратной части |
log4cpp | Windows: 10ms Linux: теор. минимальное значение 1ns, зависит от аппаратной части |
P7 | Windows: 100ns Linux: теор. минимальное значение 1ns, зависит от аппаратной части |
G3log | Windows: 1ms Linux: 1us |
Spdlog | Windows: 1ms Linux: теор. минимальное значение 1ns, зависит от аппаратной части |
Easylogging | Windows: 1ms Linux: 1us |
Производительность
Очень многие логгеры из приведенного в данной статье списка заявляют, что одним из главных приоритетов для них является производительность.
Я отнесся к этому заявлению более чем серьезно и провел ряд тестов:
- в конфигурации по умолчанию
- в условиях равного использования памяти
- в режиме одного потока
- в режиме много-поточности
Для каждого теста делается замер времени и CPU потраченного логгером на сохранение 1 миллиона сообщений в файл. Проводятся 3 замера и вычисляются средние показатели.
Так же проводились тесты в debug (оптимизация отключена) и release (оптимизация O2) сборке. Для тестов использовалась следующая конфигурация:
- Window 7x64 (6.1.7601 Service Pack 1 Build 7601)
- Visual studio 2015, update 2
- RAM: 16Gb (DDR3-1600 / PC3-12800)
- CPU: Intel Core i7-870
- HDD: Samsung EVO SSD 850 256GB (SATA3)
В целях приблизить тесты к реальному использованию следующая информация сохранялась для каждого лог сообщения:
- Имя исходного файла
- Номер строки кода
- Имя исходной функции
- Имя логгера/модуля
- Уровень (error, warning, …)
- Время
- ID текущего потока (thread Id)
- Номер ядра CPU
- Текстовое сообщение
Код который исполнялся для каждого логгера (для компиляции требуется поддержка С++11):
#include <stdio.h>
#include <atomic>
#include <thread>
#include <vector>
//Include specific logger headers
#include "Logger headers ..."
using namespace std;
using namespace std::chrono;
//Use this macro to switch on multi-threading mode
//#define MULTI_THREAD
int main(int argc, char* argv[])
{
//Logger initialization
//..
unsigned int thread_count = 4;
unsigned int howmany = 1'000'000;
vector<thread> threads;
auto start = system_clock::now();
#if !defined(MULTI_THREAD)
for(unsigned int i=0; i < howmany; i++)
{
//Has to be customized for every logger
LOG(INFO) << " Message + all required information, #" << i;
}
#else
howmany /= thread_count;
for (int t = 0; t < thread_count; ++t)
{
threads.push_back(std::thread([&]
{
for(unsigned int i=0; i < howmany; i++)
{
//Has to be customized for every logger
LOG(INFO) << " Message + all required information, #" << i;
}
}));
}
for(auto &t:threads)
{
t.join();
};
howmany *= thread_count;
#endif
auto delta = system_clock::now() - start;
auto delta_d = duration_cast<duration<double>> (delta).count();
LOG(INFO) << "Time = " << (double)howmany / delta_d << " per second, total time = " << delta_d;
//Logger uninitialization if necessary
return 0;
}
Один поток
Один поток должен сохранить 1 миллион сообщений в файл, фильтрация отключена, ротация файлов отключена. Измеряется время исполнения и среднее использование CPU.
Debug Time (ms) |
Debug CPU (%) |
Release Time (ms) |
Release CPU (%) |
|
---|---|---|---|---|
Pantheios | 140 300 | 13% | 28 400 | 13% |
Glog | 52 500 | 13% | 8 270 | 13% |
log4cpp | 130 570 | 13% | 13 806 | 13% |
P7 (1)(2) | 520 | 14% | 100 | 14% |
G3log (1) (3) | 102 990 | 38% | 3 660 | 37% |
Spdlog (1) (4) | 64 250 | 13% | 869 | 13% |
Spdlog (1) (5) | 65 660 | 13% | 885 | 13% |
Easylogging | 271 060 | 13% | 9 100 | 13% |
- Асинхонное логирование
- Скомпилирован с опцией «/P7.Pool=1024» — общий объем доступной памяти 1 мегабайт.
- Замеры можно считать синтетическими, так как большую часть времени логгер складывает данные в буфера, а запись происходит по выходу из приложения и эта работа занимает время превышающее время логирования на порядок, 1 секунда логирования и 10 секунд сохранения данных.
- Логирование в условиях рекомендованных производителем «spdlog::set_async_mode(1048576)» при этом логгер потребляет около 250 мегабайт памяти
- Логирование в условиях равного потребления памяти «spdlog::set_async_mode(4096)» — в этом случае у логгера выделен буфер из 4k элементов, каждый элемент занимает около 250 байт что в итоге дает потребление памяти около 1 мегабайта.
4 потока
4 потока должны суммарно сохранить 1 миллион сообщений в файл, фильтрация отключена, ротация файлов отключена.
Измеряется время исполнения и среднее использование CPU.
Debug Time (ms) |
Debug CPU (%) |
Release Time (ms) |
Release CPU (%) |
|
---|---|---|---|---|
Pantheios | 10 600 | 48% | 9 500 | 48% |
Glog | 30 200 | 93% | 5 900 | 93% |
log4cpp | 149 600 | 18% | 16 900 | 19% |
P7 (1)(2) | 790 | 19% | 230 | 19% |
G3log (1)(3) | 39 700 | 75% | 2 300 | 75% |
Spdlog (1)(4) | 11 510 | 13% | 270 | 25% |
Spdlog (1)(5) | 73 240 | 25% | 4 653 | 25% |
Easylogging | 328 230 | 19% | 8 575 | 25% |
- Асинхонное логирование
- Скомпилирован с опцией «/P7.Pool=1024» — общий объем доступной памяти 1 мегабайт.
- Замеры можно считать синтетическими, так как большую часть времени логгер складывает данные в буфера, а запись происходит по выходу из приложения и эта работа занимает время превышающее время логирования на порядок, 1 секунда логирования и 10 секунд сохранения данных.
- Логирование в условиях рекомендованных производителем «spdlog::set_async_mode(1048576)» при этом логгер потребляет около 250 мегабайт памяти
- Логирование в условиях равного потребления памяти «spdlog::set_async_mode(4096)» — в этом случае у логгера выделен буфер из 4k элементов, каждый элемент занимает около 250 байт что в итоге дает потребление памяти около 1 мегабайта.
Фильтрация
логгер должен обработать 1 миллион сообщений и отфильтровать их, т.е. в итоговый файл не попадет ни 1 сообщения.
Измеряется время исполнения.
Debug 1 thread, time (ms) |
Debug 4 threads, time (ms) |
Release 1 thread, time (ms) |
Release 4 threads, time (ms) |
|
---|---|---|---|---|
Pantheios (1) | - | - | - | - |
Glog | 55 520 | 28 240 | 6 840 | 4 790 |
log4cpp | 200 | 70 | 80 | 45 |
P7 | 84 | 102 | 23 | 42 |
G3log | 5 530 | 1950 | 24 | 9 |
Spdlog | 269 | 134 | 6 | 32 |
Easylogging (2) | - | - | - | - |
- Фильтрация не доступна по умолчанию
- Не получилось заставить работать фильтрацию
Обзор производительности
Производительность многих логгеров оказалась на очень хорошем уровне.
К сожалению, почти у всех логгеров кроме P7 наблюдается колоссальный разрыв производительности между debug и release сборкой, порой коэффициент достигает 74 (Spdlog: 65660 / 885). Это может усложнить отладку проектов в силу возросших задержек при логировании.
Произведенные тесты в определенном смысле можно назвать синтетическими, так как ни 1 разработчик, внедряющий в свое приложение библиотеку логирования, не желает, чтоб та выделяла под свои нужды 250 мегабайт памяти или потребляла 75% CPU или больше.
Обычно разработчик-интегратор желает, чтоб библиотека была незаметна и делала свое дело с минимальными требованиями к оборудованию, особенно если речь идет о маленьких встраиваемых системах.
Поэтому я сделал пересчет лучших показателей каждого логгера (тесты с равным потреблением памяти) с целью определить, сколько может быть записано лог сообщений, используя только 1% CPU и разумный, но все еще большой объем памяти равный 1mb
Формула расчёта:
((1 000 ms / время теста в ms) * 1 000 000 сообщений) / использование СPU в тесте
Количество сообщений в сек при использовании 1% CPU | |
---|---|
P7 | 714 285 сообщений (1000000 * (1000 / 100 ms) / 14%) |
Spdlog | 148 148 сообщений (1000000 * (1000 / 270 ms) / 25%) |
Glog | 9 301 сообщений (1000000 * (1000 / 8270 ms) / 13%) |
Easylogging | 8 453 сообщений (1000000 * (1000 / 9100 ms) / 13%) |
G3log | 7 384 сообщений (1000000 * (1000 / 3660 ms) / 37%) |
log4cpp | 5 571 сообщений (1000000 * (1000 / 13806 ms) / 13%) |
Pantheios | 2 708 сообщений (1000000 * (1000 / 28400 ms) / 13%) |
Выводы
Pantheios
Эта библиотека произвела достаточно смешанные впечатления, с одной стороны к проекту автор подошел основательно и вдумчиво, хорошие обзоры функционала, документация, с другой стороны низкая производительность (хотя автор отмечает обратное (1)), отсутствие ротации файлов и прочих мелочей сильно портят общее впечатление.
Итак, плюсы библиотеки:
- Type-safe вызовы основанные на шаблонах
- Хорошая и полная документация
- Один из немногих логгеров, что поддерживает юникод, правда с небольшими ограничениями
- Низкое потребление памяти
- Большое количество поддерживаемых Sink
- Большое количество поддерживаемых компиляторов
Минусы:
- Разработка судя по всему остановлена
- Статическое связывание на этапе компиляции логгера и sink (file, network, etc.)
- Невероятно долгая компиляция даже на мощных компьютерах и как опосредованный результат – размер бинарных файлов
- Самая низкая производительность из всех рассмотренных логгеров
- Конфигурация логгера только в коде (нет поддержки конфигурационных файлов, командной строки …)
- Нет поддержки ротации файлов
- Синхронное исполнение, т.е. все задержки Sink (запись в файл например) отразятся на скорости исполнения Вашего кода.
- Нет возможности комбинировать юникод и ANSI строки внутри одного сообщения
- Высокий порог вхождения (достаточно сложна в изучении и запутанна)
- It's incredibly efficient, and is faster than all other serious C++ diagnostic logging libraries by a huge margin (up to two orders of magnitude) www.pantheios.org/essentials.html
N.B.: Библиотека одна из самых больших и сложных из всех сравниваемых, поэтому высока вероятность, что многие ее возможности не были рассмотрены.
Glog
Несмотря на то, что библиотека достаточно бедна функционалом и не блещет производительностью, зато эта библиотека вдохновила ряд других проектов, которые переросли своего «отца» считай на голову. Только это уже является жирным плюсом в карму авторам.
Плюсы библиотеки:
- Простая в понимании и обслуживании библиотека
- Поддержка условного логирования (макросы)
- Поддержка сбоев процесса
- Возможность конфигурации через командную строку
- Приемлемая производительность
- Низкое потребление памяти
Минусы:
- Очень слабая документация
- Слабая поддержка сбоев процесса – ограниченное количество сигналов, только Linux
- Высокие накладные расходы при фильтрации сообщений
- Многократный разрыв (до 9 раз) в производительности Debug и Release кода
- Синхронное исполнение, т.е. все задержки Sink (запись в файл например) отразятся на скорости исполнения Вашего кода.
- Активная разработка приостановлена
- Нет поддержки юникода
- Не известна лицензия проекта
- Точность временных меток недостаточна
- Высочайшее потребление CPU в тестах на много поточность при очень скромном выигрыше в производительности логгера по сравнению с 1 потоком
log4cpp
Еще один заслуженный логгер, произвел достаточно хорошее впечатление, никаких больших разочарований, никаких громких рекламных акций, никаких невыполненных обещаний.
Плюсы библиотеки:
- Генерируемая документация
- Большое количество поддерживаемых Sink
- Низкое потребление памяти
- Поддержка конфигурационных файлов
- Хорошая эффективность фильтрации сообщений
Минусы:
- Синхронное исполнение, т.е. все задержки Sink (запись в файл например) отразятся на скорости исполнения Вашего кода.
- Нет поддержки юникода
- Многократный разрыв (до 10 раз) в производительности Debug и Release кода
- Низкая производительность (предпоследнее место)
- Размер строки не должен превышать 1024 символа – иначе ассерт
- Если путь к лог файлам не существует – генерируется exception
P7
Один из самых необычных логгеров среди рассмотренных в данной статье, в комплект поставки входит не только логгер, но и сервер для приема сообщений по сети, просмотра лог файлов, фильтрации и экспорта. Как и в случае со SpdLog прицел был на производительность и возможность использовать на встраиваемых устройствах, так как проект заточен на сеть и точное управление памятью.
Стоит отметить, что проект использует в качестве лог файлов бинарный формат и для просмотра/конвертации требуется бесплатный софт.
Плюсы библиотеки:
- Полная документация
- Высокая производительность + высокая точность временных меток, большой отрыв от ближайшего конкурента SpdLog
- Поддержка юникода
- Полный контроль над потреблением памяти
- Асинхронный – вызов Log функции независим от записи данных, таким образом нивелируются задержки на вызов функции.
- Использование Shared memory (вместо «глобальных» переменных) для доступа к логгерам и Sink
- В комплект входят обертки для C, C#, Python
- Помимо логов библиотека поддерживает запись и мониторинг в реальном времени телеметрии.
Так
- Возможность управлять удаленно (по сети) уровнем логирования на каждом устройстве, процессе или модуле, включать/выключать счетчики телеметрии
- Не требователен к процессору
- Доступен для нескольких языков и если разные части приложения написаны на C#, C++, C все они могут использовать один логгер
Минусы:
- Сложность в написании custom Sink
- Высоко интенсивное использование логгера из нескольких потоков снижает (в 2.3 раза) производительность логгера (плата за последовательность лог сообщений и временных меток)
- Лог файл имеет бинарный формат, требуется специальный софт для просмотра или конвертации в текстовую форму (формат файла открытый)
- Нет поддержки SysLog, на замену ему предлагается другой сетевой протокол с возможностью обратной связи
- Нет автоматической обработки сбоев процесса – оставлено на интегратора, есть функция по сбросу буферов
G3log
Наследник G2Log который в свою очередь был результатом переосмысления Glog. Автор преследовал в первую очередь производительность и ведет достаточно активную просветительскую работу на этот счет (1)(2).
Плюсы библиотеки:
- Хорошая скорость фильтрации
- Обработка сбоев процесса (генерация stack trace при наличии информации)
- В целом удовлетворительная документация
- Асинхронный – вызов Log функции независим от записи данных, таким образом нивелируются задержки на вызов функции.
Минусы:
- Нет поддержки юникода
- Слабая производительность, при этом высокое потребление ресурсов CPU
- Замеры производительности можно считать синтетическими, так как большую часть времени логгер складывает данные в буфера, а запись происходит по выходу из приложения и эта работа занимает время превышающее время логирования на порядок
- Полная асинхронность – временные метки и сообщения могут быть перемешаны группами или по одному при интенсивном логировании
- Неконтролируемое выделение памяти достигающие сотен мегабайт иногда гигабайт при интенсивном логировании
- Конфигурация логгера только посредством кода
- Точность временных меток недостаточна
- Многократный разрыв (до 30 раз) в производительности Debug и Release кода
- Высокое потребление CPU
- Компиляторы с поддержкой С++11 или выше
- kjellkod.wordpress.com/2014/08/16/presenting-g3log-the-next-version-of-the-next-generation-of-loggers
- kjellkod.wordpress.com/2015/06/30/the-worlds-fastest-logger-vs-g3log
Spdlog
Еще один логгер написанный с прицелом на производительность и можно сказать, что автор в этом преуспел, единственный минус — потребление памяти. Функционал стандартен для многих других логгеров, основной упор – скорость.
Плюсы библиотеки:
- Скорость, второе место в общем зачете
- В целом удовлетворительная документация
- Не требователен к процессору
- Асинхронный – вызов Log функции независим от записи данных, таким образом нивелируются задержки на вызов функции.
- Частичный контроль потребления памяти
- Хороший набор вспомогательных макросов
Минусы:
- Нет обработки сбоев процесса
- Полная асинхронность – временные метки и сообщения могут быть перемешаны группами или по одному при интенсивном логировании в итоговом файле
- Для достижения оптимальной производительности потребляет около 250-300 мегабайт оперативной памяти, при уменьшении объема памяти производительность снижается почти в 18 раз.
- Нет поддержки юникода
- Только заголовочные файлы, что я отношу к минусу, так как включение этих файлов в каждый файл Вашего проекта существенно замедляет компиляцию
- Конфигурация логгера только в коде
- Точность временных меток недостаточна, особенно в свете достигаемой производительности (3.7 миллиона сообщений в секунду при разрешении временных меток в 1ms)
- Компиляторы с поддержкой С++11 или выше
- Многократный разрыв (до 74 раз) в производительности Debug и Release кода
Easylogging
Логгер с прицелом на “light-weight”, единственный заголовочный файл (около 6700 строк кода). Функционал стандартен для многих других логгеров.
Плюсы библиотеки:
- В целом удовлетворительная документация
- Не требователен к процессору
- Умеренное потребления памяти
- Хороший набор вспомогательных макросов
- Поддержка конфигурационных файлов, командной строки
- Заявлен большое количество поддерживаемых платформ
Минусы:
- Синхронное исполнение, т.е. все задержки Sink (запись в файл например) отразятся на скорости исполнения Вашего кода.
- Низкая производительность
- Не получилось завести фильтрацию, возможно опция находится в процессе разработки
- Заявлена поддержка юникода, но по факту символы юникода не достигают файла и теряются по дороге
- Только заголовочный файл, что я отношу к минусу, так как включение этого файла в каждый файл Вашего проекта существенно замедляет компиляцию
- Точность временных меток недостаточна
- Компиляторы с поддержкой С++11 или выше
- Многократный разрыв (до 40 раз) в производительности Debug и Release кода
- Необходимо включать потоко-безопасность специальным макросом (ELPP_THREAD_SAFE), по умолчанию отключено
Итог
Внимание!
- Если Вам нужен заслуженный логгер без повышенных требований к производительности, но гибкий, с иерархической системой фильтрации – выбирайте «log4cpp» — это рабочая лошадка многих проектов
- Если Вам нужна экстремальная производительность, точный контроль памяти, удаленное управление – выбирайте P7, у Вас будет мощный инструмент мониторинга вашего софта даже на очень скромном железе, по принципу – пишем все, а разберемся потом
- Синтетические тесты производителей G3Log, SpdLog совершенно не позволяют мне их рекомендовать, особенно в свете потребления памяти и процессора, возникает вопрос – где еще меня пытались очаровать рекламными заявлениями которые верны только если читать мелкий шрифт
- Easylogging произвел впечатление развивающегося проекта в котором многие функции еще недостаточно отполированы, может в будущем это будет прекрасный логгер
- Glog — проект имел очень хороший старт, но к сожалению так и не вышел на марафонскую дистанцию, зато дал заряд идей и бодрости другим проектам
- Pantheios — добротный логгер, правда с некоторыми упущениями в функционале. Из-за очень низкой производительности применение ограничено
Спасибо большое за внимание и Ваше время, надеюсь эта статья помогла Вам в составлении общего представления о реалиях современных логгеров.
Автор: ostryh