#include "Logger.h" #include #include #include #include #include #include namespace RenderCadenceCompositor { namespace { int Rank(LogLevel level) { switch (level) { case LogLevel::Log: return 0; case LogLevel::Warning: return 1; case LogLevel::Error: return 2; default: return 0; } } std::string FormatTimestamp(std::chrono::system_clock::time_point timestamp) { const std::time_t time = std::chrono::system_clock::to_time_t(timestamp); std::tm localTime = {}; localtime_s(&localTime, &time); std::ostringstream stream; stream << std::put_time(&localTime, "%Y-%m-%d %H:%M:%S"); return stream.str(); } std::string FormatRecord(const Logger::Record& record) { std::ostringstream stream; stream << FormatTimestamp(record.timestamp) << " [" << LogLevelName(record.level) << "]" << " [" << record.subsystem << "] " << record.message; return stream.str(); } } const char* LogLevelName(LogLevel level) { switch (level) { case LogLevel::Log: return "log"; case LogLevel::Warning: return "warning"; case LogLevel::Error: return "error"; default: return "log"; } } Logger& Logger::Instance() { static Logger logger; return logger; } Logger::~Logger() { Stop(); } void Logger::Start(LoggerConfig config) { std::lock_guard lock(mMutex); if (mRunning) return; mConfig = config; mStopping = false; mRunning = true; OpenFileSink(); { std::lock_guard countersLock(mCountersMutex); mCounters = LoggerCounters(); } mThread = std::thread([this]() { ThreadMain(); }); } void Logger::Stop() { { std::lock_guard lock(mMutex); if (!mRunning && !mThread.joinable()) return; mStopping = true; } mCondition.notify_all(); if (mThread.joinable()) mThread.join(); std::lock_guard lock(mMutex); CloseFileSink(); mRunning = false; } void Logger::Write(LogLevel level, const std::string& subsystem, const std::string& message) { Enqueue(Record{ std::chrono::system_clock::now(), std::this_thread::get_id(), level, subsystem, message }, true); } bool Logger::TryWrite(LogLevel level, const std::string& subsystem, const std::string& message) { return Enqueue(Record{ std::chrono::system_clock::now(), std::this_thread::get_id(), level, subsystem, message }, false); } void Logger::Log(const std::string& subsystem, const std::string& message) { Write(LogLevel::Log, subsystem, message); } void Logger::Warning(const std::string& subsystem, const std::string& message) { Write(LogLevel::Warning, subsystem, message); } void Logger::Error(const std::string& subsystem, const std::string& message) { Write(LogLevel::Error, subsystem, message); } LoggerCounters Logger::Counters() const { std::lock_guard lock(mCountersMutex); return mCounters; } bool Logger::IsRunning() const { std::lock_guard lock(mMutex); return mRunning; } bool Logger::ShouldWrite(LogLevel level) const { return Rank(level) >= Rank(mConfig.minimumLevel); } bool Logger::Enqueue(Record record, bool block) { if (!ShouldWrite(record.level)) return true; std::unique_lock lock(mMutex, std::defer_lock); if (block) { lock.lock(); } else if (!lock.try_lock()) { std::lock_guard countersLock(mCountersMutex); ++mCounters.dropped; return false; } if (!mRunning) { lock.unlock(); WriteRecord(record); return true; } if (mQueue.size() >= mConfig.maxQueuedMessages) { std::lock_guard countersLock(mCountersMutex); ++mCounters.dropped; return false; } mQueue.push_back(std::move(record)); { std::lock_guard countersLock(mCountersMutex); ++mCounters.queued; } lock.unlock(); mCondition.notify_one(); return true; } void Logger::ThreadMain() { for (;;) { Record record; { std::unique_lock lock(mMutex); mCondition.wait(lock, [this]() { return mStopping || !mQueue.empty(); }); if (mQueue.empty()) { if (mStopping) return; continue; } record = std::move(mQueue.front()); mQueue.pop_front(); } WriteRecord(record); } } void Logger::WriteRecord(const Record& record) { const std::string line = FormatRecord(record); if (mConfig.writeToDebugOutput) OutputDebugStringA((line + "\n").c_str()); if (mConfig.writeToConsole) { if (record.level == LogLevel::Error) std::cerr << line << "\n"; else std::cout << line << "\n"; } if (mFile.is_open()) { mFile << line << "\n"; mFile.flush(); } std::lock_guard lock(mCountersMutex); ++mCounters.written; } void Logger::OpenFileSink() { CloseFileSink(); if (!mConfig.writeToFile || mConfig.filePath.empty()) return; std::error_code error; const std::filesystem::path logPath(mConfig.filePath); if (logPath.has_parent_path()) std::filesystem::create_directories(logPath.parent_path(), error); mFile.open(logPath, std::ios::out | std::ios::app); } void Logger::CloseFileSink() { if (mFile.is_open()) mFile.close(); } bool TryLog(LogLevel level, const std::string& subsystem, const std::string& message) { return Logger::Instance().TryWrite(level, subsystem, message); } void Log(const std::string& subsystem, const std::string& message) { Logger::Instance().Log(subsystem, message); } void LogWarning(const std::string& subsystem, const std::string& message) { Logger::Instance().Warning(subsystem, message); } void LogError(const std::string& subsystem, const std::string& message) { Logger::Instance().Error(subsystem, message); } }