From 52f9bf2057e1b53984dbea9092b6b18295351071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=B5=D1=80=D0=B3=D0=B5=D0=B9=20=D0=9C=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 15 Oct 2025 20:21:39 +0700 Subject: [PATCH] bump --- Makefile | 4 + glogger.hpp | 466 ++++++++++++++++++++++++++++++++++++++---------------------- main.c | 85 +++++++---- 3 files changed, 355 insertions(+), 200 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..563940c --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +run: all + ./a.out +all: + g++ -std=c++17 -pthread main.c diff --git a/glogger.hpp b/glogger.hpp index 141a8cf..0553640 100644 --- a/glogger.hpp +++ b/glogger.hpp @@ -12,196 +12,316 @@ #include #include #include +#include +#include #include +// Forward declarations +class DaemonLogger; + +// The Severity enum is defined outside the class to be available to the template helper, +// resolving the incomplete type compilation error. +enum class Severity { Debug, Info, Warning, Error, _Count }; + + +// Вспомогательный шаблонный класс для управления каскадными свойствами +template +class CascadingPropertyResolver { +private: + std::mutex mtx_; + T global_default_; + std::unordered_map category_defaults_; + std::unordered_map> severity_specific_; + +public: + void setGlobal(T value) { + std::lock_guard lock(mtx_); + global_default_ = value; + } + + void setForCategory(const std::string& category, T value) { + std::lock_guard lock(mtx_); + category_defaults_[category] = value; + } + + void setForSeverity(const std::string& category, Severity severity, T value) { + std::lock_guard lock(mtx_); + severity_specific_[category][severity] = value; + } + + T resolve(const std::string& category, Severity severity) { + std::lock_guard lock(mtx_); + // Правило 1: Ищем [категория, уровень] + if (auto cat_it = severity_specific_.find(category); cat_it != severity_specific_.end()) { + if (auto sev_it = cat_it->second.find(severity); sev_it != cat_it->second.end()) { + return sev_it->second; + } + } + // Правило 2: Ищем [категория] + if (auto cat_it = category_defaults_.find(category); cat_it != category_defaults_.end()) { + return cat_it->second; + } + // Правило 3: Используем глобальное значение + return global_default_; + } +}; + + class DaemonLogger { public: - enum class Severity { - Debug, // 0 - Info, // 1 - Warning, // 2 - Error, // 3 - _Count // 4 - для размера массива - }; + // Re-expose Severity enum inside the class for clarity and namespacing, + // while the actual definition is outside. + using Severity = ::Severity; private: - // Обертка для потокобезопасной записи в один ostream - struct SynchronizedStream { - std::ostream* stream; - std::mutex mtx; + // Обертка для потокобезопасной записи в один ostream + struct SynchronizedStream { + std::ostream* stream; + std::mutex mtx; + SynchronizedStream(std::ostream& s) : stream(&s) {} + }; + + // Контекст, передаваемый каждому функтору формата + struct LogContext { + const std::string& category; + Severity severity; + const std::string& location; + const std::string& user_message; + // Кэш для временной метки, чтобы не вычислять ее многократно + mutable std::optional time_cache; + }; - SynchronizedStream(std::ostream& s) : stream(&s) {} - }; + // Определяем типы для скомпилированного формата + using LogPieceFormatter = std::function; + using CompiledFormat = std::vector; public: - // --- Глобальная конфигурация маршрутизации --- - - // Устанавливает поток по умолчанию, если другие правила не совпали - static void setGlobalOutputStream(std::ostream& stream) { - std::lock_guard 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 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 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 lock(registry_mutex_); - auto it = registry_.find(category); - if (it == registry_.end()) { - auto logger = std::unique_ptr(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 - 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(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 lock(target_synced_stream->mtx); - *target_synced_stream->stream << final_message.str() << std::endl; - } - } + // --- Конфигурация маршрутизации потоков --- + static void setGlobalOutputStream(std::ostream& stream) { + stream_resolver_.setGlobal(get_or_create_synced_stream(stream)); + reconfigureAllLoggers(); + } + static void setCategoryOutputStream(const std::string& category, std::ostream& stream) { + stream_resolver_.setForCategory(category, get_or_create_synced_stream(stream)); + reconfigureLogger(category); + } + static void setSeverityOutputStream(const std::string& category, Severity severity, std::ostream& stream) { + stream_resolver_.setForSeverity(category, severity, get_or_create_synced_stream(stream)); + reconfigureLogger(category); + } + + // --- Конфигурация форматов --- + static void setGlobalFormat(const std::string& format) { + format_resolver_.setGlobal(compileFormat(format)); + reconfigureAllLoggers(); + } + static void setCategoryFormat(const std::string& category, const std::string& format) { + format_resolver_.setForCategory(category, compileFormat(format)); + reconfigureLogger(category); + } + static void setSeverityFormat(const std::string& category, Severity severity, const std::string& format) { + format_resolver_.setForSeverity(category, severity, compileFormat(format)); + reconfigureLogger(category); + } + + // --- Получение и настройка логгера --- + static DaemonLogger& get(const std::string& category) { + std::lock_guard lock(registry_mutex_); + if (auto it = registry_.find(category); it != registry_.end()) { + return *(it->second); + } + auto logger = std::unique_ptr(new DaemonLogger(category)); + logger->resolveProperties(); + auto [inserted_it, _] = registry_.try_emplace(category, std::move(logger)); + return *(inserted_it->second); + } + + void setMinSeverity(Severity level) { min_severity_ = level; } + void suppress() { suppressed_ = true; } + void unsuppress() { suppressed_ = false; } + + // --- Основной метод логирования --- + template + void log(Severity severity, const std::string& source_location, const std::string& format, Args... args) const { + if (suppressed_ || severity < min_severity_) return; + + size_t severity_idx = static_cast(severity); + auto& target_stream = resolved_streams_[severity_idx]; + auto& compiled_format = resolved_compiled_formats_[severity_idx]; + + if (!target_stream || !target_stream->stream || !compiled_format || compiled_format->empty()) return; + + // 1. Форматируем только пользовательскую часть сообщения + 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()) + " !!!"; + } + + // 2. Создаем контекст + LogContext context{category_, severity, source_location, user_message, std::nullopt}; + + // 3. Быстро собираем итоговое сообщение, исполняя скомпилированный план + std::ostringstream final_message_stream; + for (const auto& formatter : *compiled_format) { + formatter(final_message_stream, context); + } + + // 4. Потокобезопасно выводим результат + { + std::lock_guard lock(target_stream->mtx); + *target_stream->stream << final_message_stream.str() << std::endl; + } + } private: - explicit DaemonLogger(std::string category) : category_(std::move(category)) {} - - // Разрешает и кэширует маршруты для этого экземпляра логгера - void resolveOutputStreams() { - std::lock_guard lock(config_mutex_); - for (size_t i = 0; i < static_cast(Severity::_Count); ++i) { - Severity s = static_cast(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 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(stream); - stream_registry_[&stream] = synced_stream; - return synced_stream; - } - - static void reconfigureAllLoggers() { - std::lock_guard 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, static_cast(Severity::_Count)> resolved_streams_; - - // --- Статические данные класса --- - inline static std::unordered_map> registry_; - inline static std::mutex registry_mutex_; - - inline static std::mutex config_mutex_; - // Хранилище правил маршрутизации - inline static std::shared_ptr global_stream_; - inline static std::unordered_map> category_defaults_; - inline static std::unordered_map>> severity_specific_; - // Реестр ostream'ов, чтобы не создавать дубликаты SynchronizedStream для одного и того же потока - inline static std::unordered_map> stream_registry_; -}; + explicit DaemonLogger(std::string category) : category_(std::move(category)) {} + + void resolveProperties() { + for (size_t i = 0; i < static_cast(Severity::_Count); ++i) { + Severity s = static_cast(i); + resolved_streams_[i] = stream_resolver_.resolve(category_, s); + resolved_compiled_formats_[i] = format_resolver_.resolve(category_, s); + } + } + + // "Компилятор" строки формата в набор функторов + static std::shared_ptr compileFormat(const std::string& format) { + static std::mutex compile_mutex; + static std::unordered_map> cache; + + std::lock_guard lock(compile_mutex); + if (auto it = cache.find(format); it != cache.end()) { + return it->second; + } + + auto compiled = std::make_shared(); + size_t last_pos = 0; + size_t brace_pos = 0; + + while ((brace_pos = format.find('{', last_pos)) != std::string::npos) { + // Добавляем литерал перед плейсхолдером + if (brace_pos > last_pos) { + compiled->emplace_back( + [literal = format.substr(last_pos, brace_pos - last_pos)](auto& os, const auto&) { + os << literal; + }); + } + + size_t end_brace_pos = format.find('}', brace_pos); + if (end_brace_pos == std::string::npos) { + // Если нет закрывающей скобки, то оставшаяся часть строки, включая '{', + // будет обработана как литерал в конце функции. + last_pos = brace_pos; + break; + } + + std::string placeholder = format.substr(brace_pos + 1, end_brace_pos - brace_pos - 1); + if (placeholder == "date") { + compiled->emplace_back([](auto& os, const auto& ctx) { + if (!ctx.time_cache) ctx.time_cache = currentTime(); + os << *ctx.time_cache; + }); + } else if (placeholder == "cat") { + compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.category; }); + } else if (placeholder == "severity") { + compiled->emplace_back([](auto& os, const auto& ctx) { os << severityToString(ctx.severity); }); + } else if (placeholder == "loc") { + compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.location; }); + } else if (placeholder == "umsg") { + compiled->emplace_back([](auto& os, const auto& ctx) { os << ctx.user_message; }); + } else { + // Неизвестный плейсхолдер - выводим как есть, включая скобки + compiled->emplace_back( + [literal = format.substr(brace_pos, end_brace_pos - brace_pos + 1)](auto& os, const auto&) { + os << literal; + }); + } + + last_pos = end_brace_pos + 1; + } + // Добавляем оставшийся хвост строки + if (last_pos < format.length()) { + compiled->emplace_back( + [literal = format.substr(last_pos)](auto& os, const auto&) { + os << literal; + }); + } + + cache[format] = compiled; + return compiled; + } + + static void reconfigureAllLoggers() { + std::lock_guard lock(registry_mutex_); + for (auto const& [key, val] : registry_) val->resolveProperties(); + } + + static void reconfigureLogger(const std::string& category) { + std::lock_guard lock(registry_mutex_); + if (auto it = registry_.find(category); it != registry_.end()) { + it->second->resolveProperties(); + } + } + + static std::shared_ptr get_or_create_synced_stream(std::ostream& stream) { + static std::mutex stream_reg_mutex; + std::lock_guard lock(stream_reg_mutex); + if (auto it = stream_registry_.find(&stream); it != stream_registry_.end()) { + return it->second; + } + auto synced_stream = std::make_shared(stream); + stream_registry_[&stream] = synced_stream; + return synced_stream; + } + + 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; + // std::localtime is not thread-safe on all platforms, but is often an acceptable trade-off. + // For a fully thread-safe solution, platform-specific alternatives (localtime_r, localtime_s) would be needed. + oss << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S"); + return oss.str(); + } + + static const char* 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, static_cast(Severity::_Count)> resolved_streams_; + std::array, static_cast(Severity::_Count)> resolved_compiled_formats_; + + // --- Статические данные класса --- + inline static std::unordered_map> registry_; + inline static std::mutex registry_mutex_; + inline static std::unordered_map> stream_registry_; + + inline static CascadingPropertyResolver> stream_resolver_; + inline static CascadingPropertyResolver> format_resolver_; +}; // --- Макросы для удобного вызова --- #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) + 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_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__) diff --git a/main.c b/main.c index a009efe..fe1cdbf 100644 --- a/main.c +++ b/main.c @@ -1,49 +1,80 @@ #include "glogger.hpp" #include +#include +#include +#include -// Создаем файлы для логов -std::ofstream network_log_file("network.log"); +// Создаем потоки для файлов, которые будут жить в течение всей программы +std::ofstream general_log_file("general.log"); std::ofstream critical_errors_file("critical.log"); +std::ofstream network_log_file("network.log"); + +void worker_thread_func(int thread_id) { + LOG_INFO("General", "Worker thread %1% started.", thread_id); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + LOG_DEBUG("Network", "heartbeat from thread %1%", thread_id); + if (thread_id % 2 == 0) { + LOG_ERROR("General", "Simulated failure in even thread %1%", thread_id); + } + LOG_INFO("General", "Worker thread %1% finished.", thread_id); +} + int main() { - // --- Настраиваем сложную маршрутизацию --- + // --- 1. Настройка маршрутизации и форматов --- - // 1. По умолчанию все логи идут в стандартный вывод (консоль) + // Глобальное правило по умолчанию: все, что не подошло под другие правила, + // идет в консоль в простом формате. DaemonLogger::setGlobalOutputStream(std::cout); + DaemonLogger::setGlobalFormat("[{severity}] {cat}: {umsg}"); - // 2. Все логи категории "Database", кроме ошибок, идут в /dev/null (игнорируются) - // Для этого создадим "пустой" поток - std::ofstream null_stream; - null_stream.setstate(std::ios_base::badbit); // Делаем поток невалидным - DaemonLogger::setCategoryOutputStream("Database", null_stream); + // Правило для категории "General": пишем в файл general.log с полным форматом. + DaemonLogger::setCategoryOutputStream("General", general_log_file); + DaemonLogger::setCategoryFormat("General", "{date} [{severity}] <{loc}> {umsg}"); - // 3. Все логи категории "Network" идут в свой файл network.log + // Правило для категории "Network": пишем в свой файл network.log. + // Формат очень короткий, т.к. предполагается большой объем логов. DaemonLogger::setCategoryOutputStream("Network", network_log_file); + DaemonLogger::setCategoryFormat("Network", "{date} -> {umsg}"); - // 4. Но ОШИБКИ из "Network" И ОШИБКИ из "Database" идут в специальный файл critical.log - DaemonLogger::setSeverityOutputStream("Network", DaemonLogger::Severity::Error, critical_errors_file); - DaemonLogger::setSeverityOutputStream("Database", DaemonLogger::Severity::Error, critical_errors_file); + // Самое специфичное правило: ошибки из категории "General" дублируются + // в отдельный файл critical.log с особым, кричащим форматом. + DaemonLogger::setSeverityOutputStream("General", DaemonLogger::Severity::Error, critical_errors_file); + DaemonLogger::setSeverityFormat("General", DaemonLogger::Severity::Error, "!!! CRITICAL [{cat}] {date} !!! {umsg} @ {loc}"); - // --- Начинаем логирование --- + // --- 2. Тестирование одиночных логов --- - LOG_INFO("Main", "Система логирования настроена. Запускаем приложение."); + LOG_INFO("Main", "Система логирования настроена. Запускаем тесты."); // Попадет в std::cout + LOG_DEBUG("General", "Это отладочное сообщение для general.log"); // Попадет в general.log + LOG_WARNING("General", "Это предупреждение для general.log"); // Попадет в general.log - // Логирование из разных категорий - LOG_DEBUG("Network", "Инициализация сетевого стека..."); // Пойдет в network.log - LOG_INFO("Network", "Соединение с %1% установлено.", "192.168.1.1"); // Пойдет в network.log + LOG_ERROR("General", "Критическая ошибка, которая пойдет в critical.log"); // Попадет в critical.log - LOG_INFO("Database", "Подключение к базе данных..."); // Пойдет в null_stream (никуда) - LOG_WARNING("Database", "Медленный запрос обнаружен."); // Пойдет в null_stream (никуда) + LOG_INFO("Network", "New connection from 192.168.1.100"); // Попадет в network.log + LOG_INFO("Network", "Packet loss detected: 5%%"); // Попадет в network.log - LOG_ERROR("Network", "Таймаут соединения с хостом %1%", "example.com"); // Пойдет в critical.log - LOG_ERROR("Database", "Не удалось выполнить транзакцию."); // Пойдет в critical.log + // --- 3. Тестирование изменения настроек на лету --- + LOG_INFO("Main", "Меняем уровень логирования для 'Network' на Warning"); + DaemonLogger::get("Network").setMinSeverity(DaemonLogger::Severity::Warning); + LOG_INFO("Network", "Эта запись НЕ должна появиться в network.log"); + LOG_WARNING("Network", "А эта запись-предупреждение ДОЛЖНА появиться."); - LOG_INFO("Scheduler", "Задача #%1% выполнена.", 123); // Правил нет, пойдет в std::cout - LOG_ERROR("Scheduler", "Не удалось запустить задачу."); // Правил нет, пойдет в std::cout + // Возвращаем обратно для многопоточного теста + DaemonLogger::get("Network").setMinSeverity(DaemonLogger::Severity::Debug); - // Установка минимального уровня для категории - DaemonLogger::get("Network").setMinSeverity(DaemonLogger::Severity::Warning); - LOG_INFO("Network", "Эта информация больше не будет записана."); // Не пройдет из-за min_severity + // --- 4. Тестирование многопоточной записи --- + LOG_INFO("Main", "Запускаем 5 потоков для стресс-теста..."); + + std::vector threads; + for (int i = 0; i < 5; ++i) { + threads.emplace_back(worker_thread_func, i + 1); + } + + for (auto& t : threads) { + t.join(); + } + + LOG_INFO("Main", "Все потоки завершили работу. Проверьте файлы general.log, critical.log и network.log"); return 0; }