master
Сергей Маринкевич 2 months ago
commit 0bc8810186

@ -0,0 +1,207 @@
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <ostream>
#include <sstream>
#include <chrono>
#include <iomanip>
#include <array>
#include <vector>
#include <boost/format.hpp>
class DaemonLogger {
public:
enum class Severity {
Debug, // 0
Info, // 1
Warning, // 2
Error, // 3
_Count // 4 - для размера массива
};
private:
// Обертка для потокобезопасной записи в один ostream
struct SynchronizedStream {
std::ostream* stream;
std::mutex mtx;
SynchronizedStream(std::ostream& s) : stream(&s) {}
};
public:
// --- Глобальная конфигурация маршрутизации ---
// Устанавливает поток по умолчанию, если другие правила не совпали
static void setGlobalOutputStream(std::ostream& stream) {
std::lock_guard<std::mutex> lock(config_mutex_);
global_stream_ = get_or_create_synced_stream(stream);
reconfigureAllLoggers();
}
// Устанавливает поток для всех уровней в указанной категории
static void setCategoryOutputStream(const std::string& category, std::ostream& stream) {
std::lock_guard<std::mutex> lock(config_mutex_);
category_defaults_[category] = get_or_create_synced_stream(stream);
if (auto it = registry_.find(category); it != registry_.end()) {
it->second->resolveOutputStreams();
}
}
// Устанавливает поток для конкретного уровня в конкретной категории
static void setSeverityOutputStream(const std::string& category, Severity severity, std::ostream& stream) {
std::lock_guard<std::mutex> lock(config_mutex_);
severity_specific_[category][severity] = get_or_create_synced_stream(stream);
if (auto it = registry_.find(category); it != registry_.end()) {
it->second->resolveOutputStreams();
}
}
// Получить логгер по категории
static DaemonLogger& get(const std::string& category) {
std::lock_guard<std::mutex> lock(registry_mutex_);
auto it = registry_.find(category);
if (it == registry_.end()) {
auto logger = std::unique_ptr<DaemonLogger>(new DaemonLogger(category));
logger->resolveOutputStreams(); // Разрешаем маршруты при создании
auto [inserted_it, _] = registry_.try_emplace(category, std::move(logger));
return *(inserted_it->second);
}
return *(it->second);
}
// --- Настройка экземпляра логгера ---
void setMinSeverity(Severity level) { min_severity_ = level; }
void suppress() { suppressed_ = true; }
void unsuppress() { suppressed_ = false; }
// --- Основной метод логирования ---
template<class... Args>
void log(Severity severity, const std::string& source_location, const std::string& format, Args... args) const {
if (suppressed_ || severity < min_severity_) {
return;
}
// 1. Быстрое получение нужного потока (O(1))
auto& target_synced_stream = resolved_streams_[static_cast<size_t>(severity)];
if (!target_synced_stream || !target_synced_stream->stream) {
return; // Маршрут не настроен / ведет в никуда
}
// 2. Форматирование сообщения (до блокировки мьютекса)
std::string user_message;
try {
user_message = (boost::format(format) % ... % args).str();
} catch (const boost::io::format_error& e) {
user_message = "!!! LOG FORMATTING ERROR: " + std::string(e.what()) + " !!!";
}
std::ostringstream final_message;
final_message << currentTime() << " [" << severityToString(severity) << "]"
<< " [" << category_ << "]"
<< " [" << source_location << "] "
<< user_message;
// 3. Потокобезопасная запись в целевой поток
{
std::lock_guard<std::mutex> lock(target_synced_stream->mtx);
*target_synced_stream->stream << final_message.str() << std::endl;
}
}
private:
explicit DaemonLogger(std::string category) : category_(std::move(category)) {}
// Разрешает и кэширует маршруты для этого экземпляра логгера
void resolveOutputStreams() {
std::lock_guard<std::mutex> lock(config_mutex_);
for (size_t i = 0; i < static_cast<size_t>(Severity::_Count); ++i) {
Severity s = static_cast<Severity>(i);
// Правило 1: Ищем [категория, уровень]
if (auto cat_it = severity_specific_.find(category_); cat_it != severity_specific_.end()) {
if (auto sev_it = cat_it->second.find(s); sev_it != cat_it->second.end()) {
resolved_streams_[i] = sev_it->second;
continue;
}
}
// Правило 2: Ищем [категория]
if (auto cat_it = category_defaults_.find(category_); cat_it != category_defaults_.end()) {
resolved_streams_[i] = cat_it->second;
continue;
}
// Правило 3: Используем глобальный поток
resolved_streams_[i] = global_stream_;
}
}
// --- Статические хелперы и данные ---
static std::shared_ptr<SynchronizedStream> get_or_create_synced_stream(std::ostream& stream) {
auto it = stream_registry_.find(&stream);
if (it != stream_registry_.end()) {
return it->second;
}
auto synced_stream = std::make_shared<SynchronizedStream>(stream);
stream_registry_[&stream] = synced_stream;
return synced_stream;
}
static void reconfigureAllLoggers() {
std::lock_guard<std::mutex> lock(registry_mutex_);
for (auto const& [key, val] : registry_) {
val->resolveOutputStreams();
}
}
static std::string currentTime() {
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);
std::ostringstream oss;
oss << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");
return oss.str();
}
static std::string severityToString(Severity severity) {
switch (severity) {
case Severity::Debug: return "DEBUG";
case Severity::Info: return "INFO ";
case Severity::Warning: return "WARN ";
case Severity::Error: return "ERROR";
default: return "UNKNW";
}
}
// --- Данные экземпляра ---
std::string category_;
Severity min_severity_ = Severity::Debug;
bool suppressed_ = false;
std::array<std::shared_ptr<SynchronizedStream>, static_cast<size_t>(Severity::_Count)> resolved_streams_;
// --- Статические данные класса ---
inline static std::unordered_map<std::string, std::unique_ptr<DaemonLogger>> registry_;
inline static std::mutex registry_mutex_;
inline static std::mutex config_mutex_;
// Хранилище правил маршрутизации
inline static std::shared_ptr<SynchronizedStream> global_stream_;
inline static std::unordered_map<std::string, std::shared_ptr<SynchronizedStream>> category_defaults_;
inline static std::unordered_map<std::string, std::unordered_map<Severity, std::shared_ptr<SynchronizedStream>>> severity_specific_;
// Реестр ostream'ов, чтобы не создавать дубликаты SynchronizedStream для одного и того же потока
inline static std::unordered_map<std::ostream*, std::shared_ptr<SynchronizedStream>> stream_registry_;
};
// --- Макросы для удобного вызова ---
#define __LOG_IMPL(category, severity, format, ...) \
do { \
std::string loc = std::string(__func__) + ":" + std::to_string(__LINE__); \
DaemonLogger::get(category).log(severity, loc, format, ##__VA_ARGS__); \
} while (0)
#define LOG_DEBUG(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Debug, format, ##__VA_ARGS__)
#define LOG_INFO(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Info, format, ##__VA_ARGS__)
#define LOG_WARNING(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Warning, format, ##__VA_ARGS__)
#define LOG_ERROR(category, format, ...) __LOG_IMPL(category, DaemonLogger::Severity::Error, format, ##__VA_ARGS__)

@ -0,0 +1,49 @@
#include "glogger.hpp"
#include <thread>
// Создаем файлы для логов
std::ofstream network_log_file("network.log");
std::ofstream critical_errors_file("critical.log");
int main() {
// --- Настраиваем сложную маршрутизацию ---
// 1. По умолчанию все логи идут в стандартный вывод (консоль)
DaemonLogger::setGlobalOutputStream(std::cout);
// 2. Все логи категории "Database", кроме ошибок, идут в /dev/null (игнорируются)
// Для этого создадим "пустой" поток
std::ofstream null_stream;
null_stream.setstate(std::ios_base::badbit); // Делаем поток невалидным
DaemonLogger::setCategoryOutputStream("Database", null_stream);
// 3. Все логи категории "Network" идут в свой файл network.log
DaemonLogger::setCategoryOutputStream("Network", network_log_file);
// 4. Но ОШИБКИ из "Network" И ОШИБКИ из "Database" идут в специальный файл critical.log
DaemonLogger::setSeverityOutputStream("Network", DaemonLogger::Severity::Error, critical_errors_file);
DaemonLogger::setSeverityOutputStream("Database", DaemonLogger::Severity::Error, critical_errors_file);
// --- Начинаем логирование ---
LOG_INFO("Main", "Система логирования настроена. Запускаем приложение.");
// Логирование из разных категорий
LOG_DEBUG("Network", "Инициализация сетевого стека..."); // Пойдет в network.log
LOG_INFO("Network", "Соединение с %1% установлено.", "192.168.1.1"); // Пойдет в network.log
LOG_INFO("Database", "Подключение к базе данных..."); // Пойдет в null_stream (никуда)
LOG_WARNING("Database", "Медленный запрос обнаружен."); // Пойдет в null_stream (никуда)
LOG_ERROR("Network", "Таймаут соединения с хостом %1%", "example.com"); // Пойдет в critical.log
LOG_ERROR("Database", "Не удалось выполнить транзакцию."); // Пойдет в critical.log
LOG_INFO("Scheduler", "Задача #%1% выполнена.", 123); // Правил нет, пойдет в std::cout
LOG_ERROR("Scheduler", "Не удалось запустить задачу."); // Правил нет, пойдет в std::cout
// Установка минимального уровня для категории
DaemonLogger::get("Network").setMinSeverity(DaemonLogger::Severity::Warning);
LOG_INFO("Network", "Эта информация больше не будет записана."); // Не пройдет из-за min_severity
return 0;
}
Loading…
Cancel
Save