#pragma once #include #include #include #include #include #include #include #include #include #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: // 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; 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; }; // Определяем типы для скомпилированного формата using LogPieceFormatter = std::function; using CompiledFormat = std::vector; public: // --- Конфигурация маршрутизации потоков --- 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 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) #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__)