logme vs spdlog vs quill: скорость и управление логами

Сравнение logme vs spdlog обычно начинают со скорости, особенно если речь идет о C++ библиотеках логирования. Это естественно: логгер находится в коде повсюду, включая горячие участки. Дорогой logging call влияет на приложение даже тогда, когда сообщение не попадает в файл. Неэффективная запись в файл тоже быстро становится bottleneck-ом.

Но скорость — только часть выбора. В реальном production-проекте логгер должен не просто быстро писать данные. Он должен помогать управлять потоком логов, не захламлять систему и не заставлять держать debug включенным постоянно. Еще важнее — он должен позволять включить нужную диагностику тогда, когда проблема уже происходит.

Именно поэтому spdlog, Quill и logme стоит сравнивать не только как logging API. Важна и модель эксплуатации.

spdlog: сильный general-purpose logger

spdlog — популярная и удобная C++ библиотека. Ее сильная сторона — простая модель: logger, sinks, форматирование, уровни, вывод в консоль или файл. Для многих приложений этого достаточно.

Проект создает logger, подключает console sink или rotating file sink, задает формат и пишет сообщения. Такой код легко читать и объяснять команде. Библиотека хорошо известна, имеет понятный API и развитую экосистему.

Но базовая модель spdlog остается моделью “logger + sinks”. Она хороша, когда структура логирования в основном известна заранее. Если нужно сложное runtime-управление диагностикой, его обычно приходится строить отдельно. Например, отдельно реализовывать временные backend-ы, выборочные каналы, управление trace points и включение подробных логов без перезапуска.

Это не делает spdlog плохим выбором. У него просто другой фокус: удобное и быстрое логирование через привычный API.

Quill: async logging и hot path

Quill делает акцент на low-latency asynchronous logging. Идея понятна: producer thread быстро кладет сообщение в очередь, а тяжелая работа уходит в backend thread. Форматирование и запись не должны мешать горячему пути приложения.

Такая модель полезна для hot path. Если приложение обрабатывает высокочастотные события, важно уменьшить задержку именно в рабочем потоке. Но async logging не делает логирование быстрее глобально.

Работа никуда не исчезает. Сообщение все равно нужно обработать, отформатировать и записать. Асинхронная модель добавляет и собственные расходы: очередь, синхронизацию, передачу данных между потоками, drain, flush и backpressure.

Если разрешить очереди расти почти без ограничений, producer latency может выглядеть очень красиво. Но это не значит, что система стала дешевле. Часто работа просто откладывается, а хвост очереди и потребление памяти растут.

Поэтому async benchmark-и нужно читать аккуратно. Важно смотреть не только на producer latency, но и на суммарную стоимость, drain time и поведение при ограниченных ресурсах.

logme: высокая производительность и контроль над логами

logme занимает другую нишу. Его сильная сторона не в том, что он просто умеет писать в файл. Сильная сторона — сочетание высокой производительности с более мощной моделью логирования.

По текущим измерениям logbench logme показывает выдающиеся результаты в большинстве важных сценариев. Особенно важна запись в файл. Это ключевой режим работы логгера, а не вспомогательная возможность.

Быстрая запись в файл

В File-сценариях logme находится среди лидеров. В ряде измерений он работает в 2–3 раза быстрее, чем spdlog. По сравнению с некоторыми другими библиотеками разница еще больше.

Для production-систем это важный результат. Если приложение пишет гигабайты логов, файловый backend должен быть очень быстрым. Иначе логирование само становится проблемой.

В реальных проектах вроде NFS сценарий еще сложнее. Там логирование — это не один logger и один файл. Каждый запрос может иметь свой канал и собственный лог. Одновременно могут существовать сотни или тысячи логов. Через систему проходят гигабайты диагностических данных.

При такой нагрузке функции logme не оказываются в верхней части профиля VTune как главный потребитель CPU. Это важнее простого синтетического теста. Такой результат показывает, что библиотека хорошо работает в реальной сложной нагрузке.

Ранний precheck и макросы

Отдельно важен Null-сценарий. Его нельзя понимать как “логирование полностью вырезано препроцессором”. В таких тестах измеряется стоимость предварительной обработки logging call. Библиотека должна быстро понять, что сообщение не нужно отправлять дальше, и не сделать лишнюю работу.

Для C/C++ это критично. Часто самая дорогая часть отключенного лога — не сам logger, а вычисление аргументов. Это может быть построение строк, вызовы функций, подготовка диагностического текста или обход структур.

Macro/precheck-модель logme позволяет остановиться раньше. Если сообщение не нужно, дорогие выражения не вычисляются до начала реальной обработки макросом. Для горячих участков кода это принципиальная разница.

Управление шумом в логах

Производительность — только часть проблемы. В большом проекте быстро появляется другая боль: логов становится слишком много. Debug нужен, но держать его включенным постоянно нельзя. Некоторые сообщения повторяются тысячами раз. Иногда нужно выполнить дополнительное действие только если лог действительно активен.

logme закрывает эти сценарии отдельными механизмами. Макросы _Do позволяют выполнять диагностический код только тогда, когда соответствующее логирование будет обработано. Это полезно, если подготовка данных сама по себе дорогая.

Макросы _Once и _Every управляют частотой появления сообщений. Они помогают не заливать лог одинаковыми строками, но сохранять важные признаки состояния. _Collapse и _CollapseIgnore позволяют сворачивать повторяющиеся сообщения и сохранять читаемость лога, когда система входит в шумный режим.

Это уже не просто быстрая запись строк. Это управление потоком логов.

Runtime-control в production

К этому добавляется runtime-control модель. В logme сообщения идут через channels, backend-ы, levels, flags, subsystem-ы и trace points. Каналы можно направлять в разные backend-ы: файл, консоль, debugger, buffer, ring buffer, callback и другие destinations.

В обычном production-режиме сервис работает тихо. Включены warnings и errors, подробный debug выключен, лишние файлы не создаются. Когда появляется проблема, диагностику можно включить точечно: для нужного канала, subsystem-а, trace point-а и backend-а. После расследования ее можно выключить обратно.

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

Async не заменяет хорошую архитектуру логирования

Иногда кажется, что асинхронная библиотека автоматически решает проблему. Producer быстро положил сообщение в очередь — значит, логирование быстрое. На практике все сложнее.

Асинхронность уменьшает latency в конкретном потоке, но не отменяет стоимость работы. Если библиотека быстро складывает сообщения в очередь, а потом долго их разгребает, это может быть приемлемо не всегда. При росте очереди появляются отложенные проблемы: память, drain time, задержки flush и поведение при shutdown.

Хорошо оптимизированная библиотека может давать сравнимую latency на hot path без лишних расходов агрессивной асинхронности. По измерениям latency видно, что logme в File-сценарии находится на очень хорошем уровне: logme (c) около 0.09 us/call, logme (fmt) около 0.15 us/call, spdlog (fmt) около 0.20 us/call.

Если разрешить Quill раздувать очередь, producer latency тоже может выглядеть очень низкой. Но бесконтрольный рост очереди — плохой production-критерий. В реальной системе ресурсы ограничены, а логгер должен вести себя предсказуемо под давлением.

Поэтому важна не только минимальная latency одного вызова. Нужно смотреть на всю модель: очередь, drain, backpressure, ограничение объема данных и управление потоком сообщений.

Почему logme — это решение проблем с логами

logme не ограничивается задачей “быстро записать сообщение”. Он закрывает несколько уровней проблемы.

На нижнем уровне — высокая производительность logging path и файловой записи. В свою очередь, на уровне C/C++ макросов — ранняя проверка и возможность не вычислять дорогие выражения. На уровне потока сообщений — _Once, _Every, _Collapse, _CollapseIgnore. А на уровне архитектуры приложения — channels, backend-ы, routing, subsystem-ы и trace points. На уровне эксплуатации — runtime control, временное включение диагностики, rotation, retention, compression и управление данными логов.

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

В этом смысле logme — не просто еще один logger. Это решение проблем, которые появляются вокруг логирования в реальных C/C++ системах.

Итог

spdlog, Quill и logme решают похожую задачу, но с разными акцентами.

spdlog — сильный и удобный general-purpose logger. Он хорошо подходит, когда нужна простая и зрелая модель logger+sinks.

Quill интересен там, где главный фокус — async producer-side latency. Но async logging нужно оценивать осторожно. Низкая latency на producer side не означает, что логирование стало дешевле в целом.

logme сочетает высокую производительность с более мощной моделью эксплуатации. Он быстро пишет в файл, дешево проходит через предварительную обработку logging call, умеет не вычислять ненужные выражения, управляет частотой и повторяемостью сообщений, поддерживает каналы, backend-ы, форматы, rotation, retention и runtime-control диагностику.

Поэтому сравнение logme со spdlog и Quill не должно сводиться к вопросу “кто быстрее в одном benchmark-е”. Важно другое: какая библиотека помогает решать реальные проблемы с логами в production.

Logme силен именно в этом. Он не просто пишет данные в лог. Он помогает управлять логированием как частью живой системы.

Добавить комментарий