#include "Logger.h" #include #include #include #include #include namespace simv::core { namespace { constexpr std::array kCategoryNames = {{ "core", "vk", "mesh", "ui", "test", }}; static_assert(kCategoryNames.size() == LogCategoryCount, "kCategoryNames must have one entry per LogCategory"); struct CategoryEntry { bool enabled {true}; LogLevel minLevel {LogLevel::Info}; double throttleSec{0.5}; std::atomic lastLogMs {INT64_MIN}; }; struct LoggerImpl { std::array entries{}; std::shared_ptr sink; std::chrono::steady_clock::time_point epoch{std::chrono::steady_clock::now()}; std::mutex initMutex; bool initialized{false}; }; LoggerImpl& Impl() { static LoggerImpl impl; return impl; } spdlog::level::level_enum ToSpd(LogLevel l) noexcept { switch (l) { case LogLevel::Trace: return spdlog::level::trace; case LogLevel::Debug: return spdlog::level::debug; case LogLevel::Info: return spdlog::level::info; case LogLevel::Warn: return spdlog::level::warn; case LogLevel::Error: return spdlog::level::err; case LogLevel::Critical: return spdlog::level::critical; default: return spdlog::level::off; } } std::int64_t NowMs() noexcept { using namespace std::chrono; return duration_cast(steady_clock::now() - Impl().epoch).count(); } } // namespace const char* CategoryName(LogCategory cat) noexcept { const auto idx = static_cast(cat); return idx < LogCategoryCount ? kCategoryNames[idx] : "?"; } Logger& Logger::Instance() noexcept { static Logger inst; return inst; } void Logger::Init() { auto& impl = Impl(); std::lock_guard lk(impl.initMutex); if (impl.initialized) return; impl.sink = spdlog::stdout_color_mt("simv"); impl.sink->set_pattern("[%H:%M:%S.%e] [%^%l%$] %v"); impl.sink->set_level(spdlog::level::trace); impl.initialized = true; } void Logger::Shutdown() { auto& impl = Impl(); std::lock_guard lk(impl.initMutex); if (!impl.initialized) return; impl.sink.reset(); spdlog::drop("simv"); impl.initialized = false; } void Logger::SetMinLevel(LogCategory cat, LogLevel lvl) noexcept { const auto idx = static_cast(cat); if (idx < LogCategoryCount) Impl().entries[idx].minLevel = lvl; } void Logger::SetThrottle(LogCategory cat, double seconds) noexcept { const auto idx = static_cast(cat); if (idx < LogCategoryCount) Impl().entries[idx].throttleSec = seconds; } void Logger::SetEnabled(LogCategory cat, bool enabled) noexcept { const auto idx = static_cast(cat); if (idx < LogCategoryCount) Impl().entries[idx].enabled = enabled; } void Logger::LogRaw(LogCategory cat, LogLevel lvl, std::string_view msg) noexcept { auto& impl = Impl(); if (!impl.initialized) Init(); const auto idx = static_cast(cat); if (idx >= LogCategoryCount) return; auto& e = impl.entries[idx]; if (!e.enabled) return; if (static_cast(lvl) < static_cast(e.minLevel)) return; // Warnings/errors bypass throttling. const bool important = (lvl == LogLevel::Warn) || (lvl == LogLevel::Error) || (lvl == LogLevel::Critical); if (!important && e.throttleSec > 0.0) { const std::int64_t nowMs = NowMs(); const std::int64_t throttle = static_cast(e.throttleSec * 1000.0); std::int64_t lastMs = e.lastLogMs.load(std::memory_order_relaxed); if (lastMs != INT64_MIN && (nowMs - lastMs) < throttle) return; e.lastLogMs.store(nowMs, std::memory_order_relaxed); } impl.sink->log(ToSpd(lvl), "[{}] {}", CategoryName(cat), msg); } } // namespace simv::core