Введение: Зачем нужно правильное логирование в C++?
Логирование — это не просто запись сообщений в файл. В production-среде это ваш основной инструмент для мониторинга, отладки и аудита. Плохо настроенное логирование может привести к потере критических данных, снижению производительности (I/O-bound) или, наоборот, к "шумовому" засорению дисков. В этой статье мы разберем лучшие практики логирования в C++, которые помогут вам избежать типичных ошибок и построить надежную систему сбора логов.
1. Выбор библиотеки: spdlog vs glog vs std::clog
Стандартная библиотека C++ предлагает лишь базовые потоки std::clog и std::cerr, которые не подходят для серьезных проектов. Рассмотрим три популярных подхода:
1.1. spdlog — современный стандарт
spdlog — это header-only библиотека с поддержкой асинхронного логирования, ротации файлов, форматирования (а-ля fmtlib) и множества приемников (файл, консоль, syslog, Kafka).
Ключевые преимущества:
- Высокая производительность (за счет асинхронных потоков)
- Гибкая настройка уровней и форматов
- Поддержка структурированного логирования (JSON)
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
int main() {
// Создаем ротируемый файловый логгер (5 файлов по 10 MB)
auto logger = spdlog::rotating_logger_mt("file_logger", "logs/app.log", 1048576 * 10, 5);
logger->set_level(spdlog::level::info);
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v");
logger->info("Приложение запущено");
logger->warn("Низкий запас памяти: {} MB", free_memory);
logger->error("Ошибка подключения к БД: {}", err.what());
return 0;
}
1.2. Google glog — проверенное решение
Библиотека glog от Google проста в использовании и имеет встроенную поддержку условного логирования и дампов стека. Минус — менее гибкое форматирование и отсутствие асинхронности.
#include <glog/logging.h>
int main(int argc, char* argv[]) {
google::InitGoogleLogging(argv[0]);
FLAGS_log_dir = "./logs";
FLAGS_stderrthreshold = google::WARNING; // Дублировать WARNING+ в stderr
LOG(INFO) << "Сервер запущен на порту " << port;
LOG_IF(WARNING, memory_usage > 0.9) << "Память почти исчерпана";
LOG(FATAL) << "Критическая ошибка, завершение";
return 0;
}
1.3. Самописный логгер на std::clog
Используйте std::clog только для отладки или очень маленьких утилит. Он не поддерживает ротацию, уровни и многопоточность.
#define LOG(level, msg) std::clog << "[" << level << "] " << msg << std::endl;
LOG("INFO", "Запуск");
LOG("ERROR", "Ошибка: " + std::to_string(errno));
2. Лучшие практики настройки и архитектуры
Даже лучшая библиотека не спасет, если архитектура логирования построена неправильно. Придерживайтесь следующих принципов:
2.1. Используйте уровни логирования
Определите 5-6 уровней и строго следуйте им:
- TRACE — детальная отладка (вход/выход из функций)
- DEBUG — отладочная информация для разработчиков
- INFO — нормальная работа приложения (запуск, завершение)
- WARN — потенциальные проблемы (низкий диск, высокая загрузка)
- ERROR — ошибки, которые не прерывают работу (исключения)
- FATAL — критическая ошибка, после которой приложение завершается
// В production выставляем WARN, чтобы не забивать логи INFO-сообщениями
logger->set_level(spdlog::level::warn);
2.2. Структурированное логирование (JSON)
Для интеграции с Elasticsearch, Logstash или Grafana Loki используйте JSON-формат. Это упрощает парсинг и анализ.
logger->set_pattern("{\"time\":\"%Y-%m-%dT%H:%M:%S%z\",\"level\":\"%l\",\"thread\":%t,\"msg\":\"%v\"}");
// В коде:
nlohmann::json j;
j["user"] = user_id;
j["action"] = "login";
logger->info("{}", j.dump());
2.3. Ротация и управление размером файлов
Никогда не пишите в один файл бесконечно. Используйте ротацию по размеру или времени. В spdlog это делается одной строкой:
// Ротация по времени: каждый час
auto daily_logger = spdlog::daily_logger_mt("daily", "logs/app.log", 0, 0);
// Ротация по размеру: 5 файлов по 50 MB
auto rotating_logger = spdlog::rotating_logger_mt("rotating", "logs/app.log", 52428800, 5);
2.4. Асинхронное логирование
В высоконагруженных системах синхронная запись в файл может стать узким местом. Используйте асинхронный режим:
#include <spdlog/async.h>
int main() {
spdlog::init_thread_pool(8192, 1); // 8192 сообщений в очереди, 1 поток записи
auto logger = spdlog::create_async<spdlog::sinks::rotating_file_sink_mt>(
"async_logger", "logs/async.log", 1048576 * 10, 3);
logger->info("Асинхронный лог");
return 0;
}