Pull to refresh

Сравнение библиотек логирования

Reading time 26 min
Views 69K


В сети огромное количество площадок формата 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х групп:

  1. Для первой группы эта статья даст более обширное сравнение логеров, чем рекомендация «логер Х для меня лучший»
  2. Для второй группы статья даст представление о текущем уровне технологий и возможно склонит чашу весов в сторону «проще использовать готовый» или «ого, работы много, но я смогу лучше» и кто знает, может через год другой мы увидим прорыв в этой области.
  3. Для третьей и самой педантичной группы я надеюсь, эта статья сохранит как минимум одну из 3х недель изысканий, в масштабах всей индустрии это может быть внушительное количество человеко-часов

N.B. Статья достаточно объемная, поэтому если решитесь на чтение, пожалуйста, запаситесь терпением!

Логеры и их основные параметры


Выбор логеров для сравнения дело хлопотное и не простое, в любом случае возникнут вопросы «А почему логер Х не был рассмотрен?» Что тут можно сказать, логика была проста – взять 4 известных логера и 4 сравнительно юных и «голодных».

Но даже сравнение этих 8 кандидатов заняло более 3х недель выкуривания доков, issues, чтения форумов, написания тестов и сбора результатов.

Так или иначе, если какой-то очень важный логер был пропущен – статью можно обновить. В обзор попали:

  1. Pantheios
  2. Glog
  3. log4cpp
  4. P7
  5. G3log
  6. Spdlog
  7. Easylogging
  8. Boost.Log (часть огромной библиотеки Boost)

Общие характеристики выбранных логеров
Лиц. Языки Обнов. Платф. Комп.
Pantheios BSD C++ 2010 Windows, *nix, OS-X VC++, GCC, Intel, Borland, Comeau,Digital Mars,Metrowerks
Glog 3-clause BSD С++ 2018 Windows, *nix, QNX VC++, GCC, clang, intel
log4cpp LGPL С++ 2017 Windows, *nix, Solaris VC++, GCC, Sun CC, OpenVMS
P7 LGPL С++, С, C#, Python 2018 Windows, *nix VC++, GCC, clang, MinGW
G3log Public Domain C++11 2018 Windows, *nix VC++, GCC, clang
Spdlog MIT C++11 2018 Windows, Linux, Solaris, OS-X, Android VC++, GCC, Clang
Easylogging MIT C++11 2018 Windows, Linux, Solaris, OS-X, Android VC++, GCC, Clang, Intel
Boost.Log Boost(1) C++ 2016 Windows, Linux(2) VC++, GCC, Clang(3)

  1. Собственная лицензия www.boost.org/LICENSE_1_0.txt
  2. www.boost.org/doc/libs/1_62_0/libs/log/doc/html/log/installation.html
  3. Есть целый ряд других платформ и компиляторов, на которых можно скомпилировать Boost, но официально они не поддерживаются и вероятно потребуют дополнительных усилий

Документация и зависимости


Трудно оспаривать важность документации для сложных проектов, современные логеры простыми проектами назвать можно с большой натяжкой и наличие хорошей документации порой существенно ускоряет внедрение и исправление ошибок:
Документация Зависимости
Pantheios Полная (API + использование) STLSoft
Glog Рудиментарная, почти отсутствует Google gflags, слабая зависимость (1)
log4cpp Генерируемая (Doxygen) (API только) Boost, слабая зависимость (1)
P7 Полная (API + использование) Нет
G3log Базовая (общие методы использования) Нет
Spdlog Базовая (общие методы использования) Нет, только заголовочный файл (2)
Easylogging Базовая (общие методы использования) Нет, только заголовочный файл (2)
Boost.Log Базовая (общие методы использования) плюс базовое описание некоторых классов и их методов Boost

  1. Слабая зависимость – часть кода зависит от сторонних решений, но не препятствует компиляции, а лишь частично ограничивает функционал.

  2. Только заголовочный файл – библиотека распространяется в виде одного или нескольких заголовочных файлов, которые необходимо включать в каждый исходный файл Вашей программы, в случае Easylogging существенно снижается скорость компиляции. Но из этой ситуации есть выход – компилировать эти проекты в виде библиотеки и подключать уже ее, правда это потребует дополнительного времени на создание проекта.

Тип логера, контроль потребления памяти и потокобезопасность


На данный момент широко распространены 2 подхода в логировании:

  • Синхронный – вызов Log(..) функции и запись в файл происходят синхронно из одного потока
  • Асинхронный – вызов Log(..) функции лишь обновляет очередь сообщений, а другой поток занимается записью, таким образом, уменьшая время вызова Log(..) функции из пользовательского потока, и как следствие достигается максимальная производительность пользовательского потока и минимизируются задержки на вызов Log(..) функции.

Правда есть нюансы понимания асинхронности разными производителями библиотек логирования.

  1. Тип №1: Одни считают, что вызов Log(..) функции должен быть атомарным и таким образом порядок лог сообщений в файле будет последовательным во времени 00:00 -> 00:01 -> 00:02 и тд.

  2. Тип №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)
Boost.Log Синхронный, Асинхронный, (тип 2) (7) нет по умолчанию (8) да

  1. Асинхронный режим находится в экспериментальном состоянии
  2. Временные метки и сообщения не перемешаны
  3. Временные метки и сообщения могут быть перемешаны
  4. По умолчанию не доступно, необходимо активировать макросом ELPP_THREAD_SAFE
  5. Неконтролируемое выделение памяти, при высоких нагрузках может выделять сотни мегабайт, комментарий автора: 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.

  6. Можно задавать длину очереди в элементах, размер элемента в начальном виде – 88 байт + текстовое сообщение (30-160 байт). Автор рекомендует задавать размер очереди в 1 миллион сообщений для оптимальной производительности, что выливается в расходы памяти от 120 мегабайт до 250 мегабайт только на библиотеку логирования.
  7. Очередность сообщений не гарантируется библиотекой по умолчанию, временные метки могут быть перемешаны в итоговом файле при логировании из разных потоков: “Why log records are weakly ordered in a multithreaded application?” Автор библиотеки предлагает использовать «unbounded_ordering_queue» для преодоления этой проблемы совместно с использованием спец. атрибута «RecordID» по которому будет производиться сортировка
  8. По умолчанию библиотека использует «unbounded_fifo_queue» что выливается в неконтролируемый рост потребления памяти при интенсивном логировании. Возможно использовать при дополнительной настройке «bounded_fifo_queue». В таком случае можно задать длину очереди в элементах. Каждый элемент (record в терминологии библиотеки) занимает около 1KB

Обработка сбоев процесса


Правильная обработка сбоев процесса (crash handling) в первую очередь важна для асинхронных логеров, т.к. часть данных хранится в буферах и если их вовремя не сохранить, то возможно самые ценные данные прямо перед сбоем будут потеряны.

Распространены 3 подхода в перехвате падений:

  • Автоматический, библиотека сама настраивает все векторы и сделает все сама, большим минусом такого решения является скудность перехватываемых сигналов, а так же помехи для приложения, которое возможно хотело бы само обрабатывать падение в целях сохранения crash dump файла или сброса буферов.
  • Ручной – библиотека предоставляет примитивы для перехвата падений и сброса буферов, а приложение уже само решает, когда и как установить перехватчик и что делать в случае падения.
  • Пусть падает, дело житейское

Pantheios Нет
Glog автоматический, только под Linux (1)
log4cpp Нет
P7 ручной и автоматический (2)
G3log автоматический, только под Linux (3)
Spdlog Нет (4)
Easylogging автоматический, только под Linux (5)
Boost.Log Нет (6)

  1. Перехватывается следующие сигналы: SIGSEGV, SIGILL, SIGFPE, SIGABRT, SIGBUS, SIGTERM.
  2. Перехватывается следующие сигналы: 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
  3. Перехватывается следующие сигналы: SIGSEGV, SIGILL, SIGFPE, SIGABRT, SIGBUS, SIGTERM, Div by zero, illegal printf, out of bounds, access violation, std::future_error
  4. github.com/gabime/spdlog/issues/55 — Дефект был «закрыт» в 2015, т.е. работ не предвидится по этому направлению.
  5. Перехватывается следующие сигналы: SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGINT
  6. Даже для синхронного режима есть риск потери данных в случае сбоя процесса, в случае использования асинхронного режима риск потери данных крайне высок

Стиль логирования и вывод (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(6) Printf Binary file, console, syslog, text file (Linux: UTF8, Windows: UTF-16) network (собств. протокол и сервер(2)), null
G3log Printf, Log() << Message File (3)
Spdlog Printf (4) File, syslog, console
Easylogging Printf (1), Log() << Message File, syslog, console
Boost.Log Log() << Message(5) File, syslog, console, Win32Debug, WinEventLog, IPC

  1. В случае использования «Printf» генерировал exception, вероятно это временная проблема.
  2. Свой сервер, используется для достижения максимальной производительности. Сервер бесплатен, но к сожалению поддерживает только Windows, судя по всему основан на Qt, запрос об поддержке Linux был направлен автору. Скорость отправки логов по сети в тестовой конфигурации была около 3.5 миллионов в секунду при загрузке CPU – 13%. Подробно тесты производительности обсуждаются в следующих главах.
  3. В официальную поставку не входит Sink с поддержкой ротации файлов и консоль, но эти расширения можно скачать github.com/KjellKod/g3sinks
  4. Формат строка для Printf функций библиотеки не совместима с каноническим форматом, что существенно затрудняет безболезненную замену одного логера другим
  5. Передача nullptr строки в качестве аргумента логеру вызывает segmentation fault/access violation
  6. Наиболее производительные Sink: Binary file, Baical

Инициализация логера


Инициализация или передача параметров достаточно важный пункт т.к. добавляет гибкости логеру и избавляет от необходимости перекомпиляции, если Вы решили изменить уровень логирования к примеру.
Pantheios Ручная (только в коде)
Glog Командная строка, ручная, переменные среды окружения
log4cpp Конфигурационный файл (1), ручная
P7 Командная строка (2), ручная
G3log Ручная (только в коде)
Spdlog Ручная (только в коде)
Easylogging Конфигурационный файл, командная строка, ручная
Boost.Log Конфигурационный файл(3), ручная

  1. Подробная и хорошо организованная настройка параметров логера, пожалуй, самая развернутая из всех.
  2. Во всех остальных логерах для того чтоб параметры командной строки были обработаны – вы должны передать их в логер руками из int main(int argc, char* argv[]) функции. В данном логере эти параметры могут быть перехвачены автоматически из любой части программы/модуля (dll,so).
  3. Библиотека предоставляет только самые базовые примитивы для конфигурации посредством конфигурационного файла. Более полное обсуждение этого вопроса можно найти по ссылке. И как заключение
    It seems to me that it is difficult to find a description of the configuration file format entries because the valid entries are derived from the source code implementing the sinks, filters etc. This implementation may be even user defined so it is impossible to give explicit configuration format description

Настройка фильтрации


Самая распространенная методика фильтрации – по уровням логирования, скажем если фильтр установлен на ERROR уровень — то все что меньше ERROR (TRACE, DEBUG, INFO, WARNING …) в лог попадать не будет. Этот метод очень удобен для отсеивания большого количества ненужной в данный момент информации и сохранения CPU и места на диске.
Pantheios Нет (1)
Glog Командная строка, ручная, переменные среды окружения
log4cpp Конфигурационный файл (4), ручная
P7 Командная строка, удаленно по сети в режиме реального времени (2) (3), ручная
G3log Нет (5)
Spdlog Ручная
Easylogging Ручная (6)
Boost.Log Конфигурационный файл, ручная

  1. Для организации фильтрации нужно разработать свой FrontEnd
  2. Поддерживается только если данные высылаются по сети, в случае записи в локальный файл сервер не имеет доступа к Verbosity level
  3. В дополнение к глобальному уровню можно устанавливать уровни для каждого модуля.
  4. Иерархические логирование и установка уровней индивидуально для каждого логера
  5. По умолнчанию отключена, включается макросом G3_DYNAMIC_LOGGING, после этого можно в ручном режиме задать уровень на все логеры. Существенно снижает производительность.
  6. Поддержка заявлена производителем, но заставить ее работать не удалось, во время использования сложилось впечатление, что функция находится в стадии разработки или заброшена.

Поддержка юникода


Эта часть тестирования была одной из самых грустных, в 2016 году поддержка юникода в таких известных библиотеках все еще находится на уровне «not officially».

Библиотека нужна для того что сохранить важные данные приложения (фамилия пользователя, путь файла, имя домена), а большинство из существующих просто не позволят Вам это сделать если данные не укладываются в тривиальный char.
Pantheios Utf-16 (1)(4), Utf-8
Glog Нет
log4cpp Нет
P7 Windows — UTF-16, *nix — UTF-8, UTF-32
G3log Нет
Spdlog Нет
Easylogging Windows Utf-16 (2), Utf-8 (3)
Boost.Log UTF-8 частично (5)

  1. Почти идеально, за исключением того что итоговый лог файл не имеет маркера юникода и кодировку в программе просмотра нужно будет выбирать самому.
  2. Поддержка заявлена, но не реализована, символы юникода в лог файл не попадают.
  3. Макрос START_EASYLOGGINGPP не поддерживает юникод
  4. В одном сообщении нельзя совместить ANSI строку скажем с UTF-16
  5. Поддержка юникода осуществляется с помощью библиотеки Boost.Locale, предоставляется впечатляюще широкая поддержка разных форматов UTF-8/16/32 и прочие национальные локали, к сожалению, заставить работать Boost.Log удалось только с UTF-8 форматом файла и только с использованием функции инициализации синхронного логера «logging::add_file_log», все остальные попытки провалились и символы юникода не достигли файла, возможно при наличии достаточного времени этот функционал можно заставить работать

Доступ к логеру


В современных библиотеках вопрос «кто владеет логером» остается за кадром, чаще всего можно написать LOG(ERROR) << “My message” и библиотека позаботится обо всем сама. Такая простота достигается с помощью глобальных переменных. Этичность использования глобальных переменных я оставлю за кадром, все таки это особый случай, но простота использования глобальной переменной в случае простого приложения оборачивается против разработчика сложного приложения состоящего из многих динамических или статических модулей.

Другой вариант получения доступа к логеру это самостоятельно создать объект и контролировать его жизненный цикл.

И последний вариант – гибридный, объекты логера создаются в ручном режиме, а затем используется глобальные переменные (registry) или разделяемая память, общая для всего процесса включая динамические модули.
Pantheios Глобальные переменные, автоматическая инициализация
Glog Глобальные переменные, автоматическая инициализация
log4cpp Глобальные переменные, автоматическая и ручная инициализация
P7 Разделяемая память, ручная инициализация
G3log Глобальные переменные, автоматическая и ручная инициализация
Spdlog Глобальные переменные, ручная инициализация
Easylogging Глобальные переменные, автоматическая инициализация
Boost.Log Глобальные переменные, автоматическая и ручная инициализация

Ротация файлов


Pantheios Нет
Glog Размер
log4cpp Размер (2)
P7 Время, размер (1) (2)
G3log Размер, по умолчанию не доступен (1)
Spdlog Размер, время(дневной) (1)
Easylogging Размер
Boost.Log Время, размер (1) (2)

  1. Каждый файл в названии содержит дату и время
  2. Поддерживается опция «макс. кол-во файлов» позволяющая хранить только N последних файлов

Точность времени


Многие из рассматриваемых в данной статье логеров разрабатывались с прицелом на высокую производительность, с потенциалом в миллионы сообщений в секунду. Но помимо высоких скоростей нужны точные временные метки высокого разрешения, т.к. если у вас в лог файле есть пара десятков или даже сотен сообщений с одинаковой временной меткой – это означает, что часть информации о времени исполнения уже утеряна.
Pantheios Windows: 10ms(1), custom back-end может помочь увеличить точность
Linux: теор. минимальное значение 1ns, зависит от аппаратной части
Glog Windows: 10ms(1)
Linux: теор. минимальное значение 1ns, зависит от аппаратной части
log4cpp Windows: 10ms(1)
Linux: теор. минимальное значение 1ns, зависит от аппаратной части
P7 Windows: 100ns
Linux: теор. минимальное значение 1ns, зависит от аппаратной части
G3log Windows: 1ms
Linux: 1us
Spdlog Windows: 1ms
Linux: теор. минимальное значение 1ns, зависит от аппаратной части
Easylogging Windows: 1ms
Linux: 1us
Boost.Log Windows: 10ms(1)
Linux: теор. минимальное значение 1ns, зависит от аппаратной части

  1. Иногда можно получить гранулярность в 1 миллисекунду, но намного чаще квант равен 10 миллисекундам.

Производительность


Очень многие логеры из приведенного в данной статье списка заявляют, что одним из главных приоритетов для них является производительность.

Я отнесся к этому заявлению более чем серьезно и провел ряд тестов:

  • в конфигурации по умолчанию
  • в условиях равного использования памяти
  • в режиме одного потока
  • в режиме много-поточности

Для каждого теста делается замер времени и 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)(6) 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%
Boost.Log(1) (7) 2 310 200 17% 44 300 9%
Boost.Log(1) (8) 649 480 25% 12 680 25%

  1. Асинхронное логирование
  2. Скомпилирован с опцией «/P7.Pool=1024» — общий объем доступной памяти 1 мегабайт.
  3. Замеры можно считать синтетическими, так как большую часть времени логер складывает данные в буфера, а запись происходит по выходу из приложения и эта работа занимает время превышающее время логирования на порядок, 1 секунда логирования и 10 секунд сохранения данных.
  4. Логирование в условиях рекомендованных производителем «spdlog::set_async_mode(1048576)» при этом логер потребляет около 250 мегабайт памяти
  5. Логирование в условиях равного потребления памяти «spdlog::set_async_mode(4096)» — в этом случае у логера выделен буфер из 4k элементов, каждый элемент занимает около 250 байт что в итоге дает потребление памяти около 1 мегабайта.
  6. Запись велась в бинарный файл, текст в формате UTF-16
  7. Логирование в условиях равного потребления памяти (asynchronous_sink + text_file_backend + bounded_ordering_queue + block_on_overflow), длина очереди 1024 элемента, каждый элемент занимает около 1100 байт, что в итоге дает потребление памяти чуть больше 1 мегабайта
  8. Синтетический тест. Логирование в максимально комфортных условиях, конфигурация логера по умолчанию (asynchronous_sink + text_file_backend + unbounded_fifo_queue). Нет ограничений в потреблении памяти, нет сортировки сообщений по порядку поступления, потребление памяти было зафиксировано на уровне 1.8GB

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)(6) 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%
Boost.Log (1)(7) 2 645 120 14% 48 290 14%
Boost.Log (1)(8) 655 470 65% 13 560 65%

  1. Асинхронное логирование
  2. Скомпилирован с опцией «/P7.Pool=1024» — общий объем доступной памяти 1 мегабайт.
  3. Замеры можно считать синтетическими, так как большую часть времени логер складывает данные в буфера, а запись происходит по выходу из приложения и эта работа занимает время превышающее время логирования на порядок, 1 секунда логирования и 10 секунд сохранения данных.
  4. Логирование в условиях рекомендованных производителем «spdlog::set_async_mode(1048576)» при этом логер потребляет около 250 мегабайт памяти
  5. Логирование в условиях равного потребления памяти «spdlog::set_async_mode(4096)» — в этом случае у логера выделен буфер из 4k элементов, каждый элемент занимает около 250 байт что в итоге дает потребление памяти около 1 мегабайта.
  6. Запись велась в бинарный файл, текст в формате UTF-16
  7. Логирование в условиях равного потребления памяти (asynchronous_sink + text_file_backend + bounded_ordering_queue + block_on_overflow), длина очереди 1024 элемента, каждый элемент занимает около 1100 байт, что в итоге дает потребление памяти чуть больше 1 мегабайта
  8. Синтетический тест. Логирование в максимально комфортных условиях, конфигурация логера по умолчанию (asynchronous_sink + text_file_backend + unbounded_fifo_queue). Нет ограничений в потреблении памяти, нет сортировки сообщений по порядку поступления, потребление памяти было зафиксировано на уровне 1.8GB

Фильтрация


логер должен обработать 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) - - - -
Boost.Log 26 407 8 554 699 389

  1. Фильтрация не доступна по умолчанию
  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 86 918 сообщений (1000000 * (1000 / 885 ms ) / 13%)
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%)
Boost.Log 2 508 сообщений (1000000 * (1000 / 44300 ms) / 9%)


Выводы


Pantheios


Эта библиотека произвела достаточно смешанные впечатления, с одной стороны к проекту автор подошел основательно и вдумчиво, хорошие обзоры функционала, документация, с другой стороны низкая производительность (хотя автор отмечает обратное (1)), отсутствие ротации файлов и прочих мелочей сильно портят общее впечатление.

Итак, плюсы библиотеки:
  • Type-safe вызовы основанные на шаблонах
  • Хорошая и полная документация
  • Один из немногих логеров, что поддерживает юникод, правда с небольшими ограничениями
  • Низкое потребление памяти
  • Большое количество поддерживаемых Sink
  • Большое количество поддерживаемых компиляторов

Минусы:
  • Разработка судя по всему остановлена
  • Статическое связывание на этапе компиляции логера и sink (file, network, etc.)
  • Невероятно долгая компиляция даже на мощных компьютерах и как опосредованный результат – размер бинарных файлов
  • Один из самых низких показателей производительности из всех рассмотренных логеров
  • Конфигурация логера только в коде (нет поддержки конфигурационных файлов, командной строки …)
  • Нет поддержки ротации файлов
  • Синхронное исполнение, т.е. все задержки Sink (запись в файл например) отразятся на скорости исполнения Вашего кода.
  • Нет возможности комбинировать юникод и ANSI строки внутри одного сообщения
  • Высокий порог вхождения (достаточно сложна в изучении и запутанна)

  1. 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


Один из самых необычных логеров среди рассмотренных в данной статье, в комплект поставки входит не только логер, но и сервер для приема сообщений по сети, просмотра лог файлов, фильтрации, экспорта, управлением уровнем verbosity удаленно и многих других функций. Как и в случае со SpdLog прицел был на производительность и возможность использовать на встраиваемых устройствах, так как проект заточен на сеть и точное управление памятью.
Стоит так же отметить, что в случае использования Sink=FileBin или Sink=Baical проект предоставляет бесплатный софт для приема по сети, просмотра, фильтации и тд.:
софт.
image


Плюсы библиотеки:
  • Полная документация
  • Высокая производительность + высокая точность временных меток, большой отрыв от ближайшего конкурента SpdLog
  • Поддержка юникода
  • Полный контроль над потреблением памяти
  • Асинхронный – вызов Log функции независим от записи данных, таким образом нивелируются задержки на вызов функции.
  • Использование Shared memory (вместо «глобальных» переменных) для доступа к логерам и Sink, что позволяет получать доступ к логеру из любых участков кода процесса, даже из динамических модулей написанных на других языка поддерживаемых библиотекой, например C# или С
  • В комплект входят обертки для C, C#, Python
  • Помимо логов библиотека поддерживает запись и мониторинг в реальном времени
    телеметрии
    image
  • Возможность управлять удаленно (по сети) уровнем логирования на каждом устройстве, процессе или модуле, включать/выключать счетчики телеметрии
  • Из всех рассмотренных логеров имеет минимальные требования к процессору в пересчете на производительность
  • Доступен для нескольких языков и если разные части приложения написаны на C#, C++, C все они могут использовать один логер
  • Простота подключения собственного Text Sink
  • Широкий выбор Sink
  • Ядро Baical сервера это open source проект и может быть скомпилировано под Windows, Linux 32/64

Минусы:
  • Высоко интенсивное использование логера из нескольких потоков снижает (в 2.3 раза) производительность логера (плата за последовательность лог сообщений и временных меток)
  • Разработка собственного Sink для бинарных данных сложна
  • Из-за сериализации лог сообщений нет возможности загрузить равномерно все ядра процессора


G3log


Наследник G2Log который в свою очередь был результатом переосмысления Glog. Автор преследовал в первую очередь производительность и ведет достаточно активную просветительскую работу на этот счет (1)(2). К сожалению, тесты производительности оказались синтетическими, и результат был далек от ожиданий.

Плюсы библиотеки:
  • Хорошая скорость фильтрации
  • Обработка сбоев процесса (генерация stack trace при наличии информации), к сожалению только под Linux
  • В целом удовлетворительная документация
  • Асинхронный – вызов Log функции независим от записи данных, таким образом нивелируются задержки на вызов функции.

Минусы:
  • Нет поддержки юникода
  • Слабая производительность, при этом высокое потребление ресурсов CPU
  • Замеры производительности можно считать синтетическими, так как большую часть времени логер складывает данные в буфера, а запись происходит по выходу из приложения и эта работа занимает время превышающее время логирования на порядок
  • Полная асинхронность – временные метки и сообщения могут быть перемешаны группами или по одному при интенсивном логировании
  • Неконтролируемое выделение памяти достигающие сотен мегабайт иногда гигабайт при интенсивном логировании
  • Конфигурация логера только посредством кода
  • Точность временных меток недостаточна
  • Многократный разрыв (до 30 раз) в производительности Debug и Release кода
  • Высокое потребление CPU
  • Компиляторы с поддержкой С++11 или выше


  1. kjellkod.wordpress.com/2014/08/16/presenting-g3log-the-next-version-of-the-next-generation-of-loggers
  2. kjellkod.wordpress.com/2015/06/30/the-worlds-fastest-logger-vs-g3log


Spdlog


Еще один логер написанный с прицелом на производительность, к сожалению хорошие показатели можно получить только при использовании сотен мегабайт оперативной памяти. Функционал стандартен для многих других логеров, основной упор – скорость.

Плюсы библиотеки:
  • В целом удовлетворительная документация
  • Не требователен к процессору
  • Асинхронный – вызов Log функции независим от записи данных, таким образом нивелируются задержки на вызов функции.
  • Частичный контроль потребления памяти
  • Хороший набор вспомогательных макросов
  • Скорость, второе место в общем зачете, но отставание от первого места коллосальное

Минусы:
  • Нет обработки сбоев процесса
  • Формат строка для Printf функций библиотеки не совместима с каноническим форматом, что существенно затрудняет безболезненную замену одного логера другим, по сути это билет в один конец
  • Полная асинхронность – временные метки и сообщения могут быть перемешаны группами или по одному при интенсивном логировании в итоговом файле
  • Для достижения оптимальной производительности потребляет около 250-300 мегабайт оперативной памяти, при уменьшении объема памяти производительность снижается почти в 18 раз.
  • Нет поддержки юникода
  • Только заголовочные файлы, что я отношу к минусу, так как включение этих файлов в каждый файл Вашего проекта существенно замедляет компиляцию, возможно использовать предварительно откомпилированные заголовки
  • Конфигурация логера только в коде
  • Точность временных меток недостаточна, особенно в свете достигаемой производительности (десятки и сотни тысяч сообщений в секунду при разрешении временных меток в 1ms)
  • Компиляторы с поддержкой С++11 или выше
  • Многократный разрыв (до 74 раз) в производительности Debug и Release кода


Easylogging


Логер с прицелом на “light-weight”, единственный заголовочный файл (около 6700 строк кода). Функционал стандартен для многих других логеров.

Плюсы библиотеки:
  • В целом удовлетворительная документация
  • Не требователен к процессору
  • Умеренное потребления памяти
  • Хороший набор вспомогательных макросов
  • Поддержка конфигурационных файлов, командной строки
  • Заявлен большое количество поддерживаемых платформ

Минусы:
  • Синхронное исполнение, т.е. все задержки Sink (запись в файл например) отразятся на скорости исполнения Вашего кода.
  • Низкая производительность
  • Не получилось завести фильтрацию, возможно опция находится в процессе разработки
  • Заявлена поддержка юникода, но по факту символы юникода не достигают файла и теряются по дороге
  • Только заголовочный файл, что я отношу к минусу, так как включение его в каждый файл Вашего проекта существенно замедляет компиляцию, возможно использовать предварительно откомпилированные заголовки
  • Точность временных меток недостаточна
  • Компиляторы с поддержкой С++11 или выше
  • Многократный разрыв (до 40 раз) в производительности Debug и Release кода
  • Необходимо включать потоко-безопасность специальным макросом (ELPP_THREAD_SAFE), по умолчанию отключено


Boost.Log


Логер с прицелом на функциональность, простоту, а так же производительность, такие приоритеты и цели преследует автор: мотивация.
Надо отдать должное, проект развивается почти 9 лет и присутствует в таком большом проекте как Boost.

Плюсы библиотеки:
  • Поддержка синхронного и асинхронного логирования (вызов Log функции независим от записи данных, таким образом нивелируются задержки на вызов функции)
  • Достаточно хорошая документация, покрывающая основные нужды
  • Атрибуты лог записей (номер потока, исходный файл, счетчики и тд) упрощают логирование
  • Поддерживается форматирование на «per sink» основе
  • Возможность использовать предикаты для фильтрации
  • Заявлено большое количество поддерживаемых Sink
  • Поддержка юникода
  • Поддержка конфигурационных файлов

Минусы:
  • Одна из самых сложных и запутанных библиотек логирования из представленных в этой статье. Целиком построена на смеси комплексных макросов и шаблонов. На изучение было потрачено больше всего времени. Еще больше времени потрачено на изучение «под капотом», возможно поэтому одним из главных участников является лишь один человек
  • Самая низкая производительность из всех рассмотренных логеров
  • Segmentation fault/access violation при выполнении участка кода
    char *pStr = nullptr;
    //….
    BOOST_LOG_SEV(lg, trace) << pStr;
    
  • Точность временных меток недостаточна, хотя с такой производительностью не столь критично (до десятка сообщений в один временной отрезок)
  • Многократный разрыв (до 54 раз) в производительности Debug и Release кода, следует отметить что в Debug режиме время исполнения становится совершенно неприемлимым (десятки минут на логирование)
  • Поддержка юникода не производит впечатление хорошо протестированного функционала (не удалось завести для асинхронного логирования в файл с сортировкой и FIFO фиксированного размера)
  • Нет обработки сбоев процесса, в свете того, что сохранение данных буферов важно даже для синхронного логера — представляет существенную опастность потери важных данных
  • Атрибуты лог записей не тривиальны – например имя исходной функции, файла, номера строки. Создание своих атрибутов так же не является тривиальной задачей
  • Запутанная компиляция со своими утилитами, но следует заметить, что поставляются так же скомпилированные бинарники, проблема общая для всего Boost
  • Включение библиотеки в свой проект существенно замедляет компиляцию и линковку (даже при использовании предварительно откомпилированных заголовков)
  • Все участки кода библиотеки покрыты генерацией исключений, правда, можно установить свой перехватчик, что уменьшает неудобства для людей предпочитающих не ловить исключения из логера при каждом обращении


Итог


Внимание!
Субъективное мнение автора.
  • Pantheios — успешный проект с точки зрения рекламы, но настолько тяжелый, неповротливый и перегруженный, что я не стал бы его подключать ни к каким проектам в которых я учавствовал
  • Glog — проект имел очень хороший старт, но к сожалению так и не вышел на марафонскую дистанцию, зато дал заряд идей и бодрости другим проектам. Пожалуй его я бы с легкостью использовал в небольших проектах не требовательых к производительности — утилиты для внутренних нужд, мини проекты и тд.
  • Log4cpp — произвел достаточно хорошее впечатление, но из-за внутренних орграничений (длинна строки, ассерты и тд) для меня ниша его использования ограничена небольшими приложениями для внутренних нужд, ничего критически важного/производительного.
  • P7 — серьезный проект, заточенный в первую очередь на сетевое использование и проекты от маленьких embedded до больших систем. Поддержка несколькиз языков, точный контроль памяти и удаленное управление, превосходная производительность, телеметрия делают его очень предпочтительным кандидатом для целого спектра задач (кроме пожалуй мелких утилит и скромных home проектов). Принцип – пишем все, а разберемся потом. Так же стоит отметить, что размер итогового файла (если Sink сконфигурирован на использование бинарныго файла) меньше файлов записанных другими протестированными логерами в несколько раз (5-7)
  • G3Log, SpdLog — синтетические тесты производительности от их авторов совершенно не позволяют мне их рассматривать серьезно, особенно в свете потребления памяти и процессора, возникает вопрос – где еще меня пытались очаровать рекламными заявлениями которые верны только если читать мелкий шрифт
  • Easylogging — произвел впечатление развивающегося проекта в котором многие функции еще недостаточно отполированы, может в будущем это будет прекрасный логгер, но пока вряд ли я буду где нибудь его использовать
  • Boost.Log — серьезный и большой проект, я испытываю уважение к автору способному поддерживать его напротяжении многих лет и удовлетворения такого большого спектра запросов, но не смотря на это вряд ли буду его использовать из-за его сложности, медлительности, возможности обрушить мое приложение просто передав nullptr как параметр. Для маленьких проектов он слишком сложен, для серьезных проектов он не достаточно стабилен и не вряд ли будет таковым опять же из-за внутренней сложности и ограниченности доступных человеческих ресурсов для полноценного поддержания проекта.


Спасибо большое за внимание и Ваше время, надеюсь эта статья помогла Вам в составлении общего представления о реалиях современных логеров.
Tags:
Hubs:
+59
Comments 42
Comments Comments 42

Articles