logging
All checks were successful
CI / React UI Build (push) Successful in 38s
CI / Native Windows Build And Tests (push) Successful in 3m12s
CI / Windows Release Package (push) Successful in 3m7s

This commit is contained in:
Aiden
2026-05-12 11:58:29 +10:00
parent 511b67c9bc
commit 44b198b14d
10 changed files with 555 additions and 12 deletions

View File

@@ -0,0 +1,283 @@
#include "Logger.h"
#include <windows.h>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <ctime>
#include <filesystem>
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<std::mutex> lock(mMutex);
if (mRunning)
return;
mConfig = config;
mStopping = false;
mRunning = true;
OpenFileSink();
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
mCounters = LoggerCounters();
}
mThread = std::thread([this]() { ThreadMain(); });
}
void Logger::Stop()
{
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mRunning && !mThread.joinable())
return;
mStopping = true;
}
mCondition.notify_all();
if (mThread.joinable())
mThread.join();
std::lock_guard<std::mutex> 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<std::mutex> lock(mCountersMutex);
return mCounters;
}
bool Logger::IsRunning() const
{
std::lock_guard<std::mutex> 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<std::mutex> lock(mMutex, std::defer_lock);
if (block)
{
lock.lock();
}
else if (!lock.try_lock())
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
++mCounters.dropped;
return false;
}
if (!mRunning)
{
lock.unlock();
WriteRecord(record);
return true;
}
if (mQueue.size() >= mConfig.maxQueuedMessages)
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
++mCounters.dropped;
return false;
}
mQueue.push_back(std::move(record));
{
std::lock_guard<std::mutex> countersLock(mCountersMutex);
++mCounters.queued;
}
lock.unlock();
mCondition.notify_one();
return true;
}
void Logger::ThreadMain()
{
for (;;)
{
Record record;
{
std::unique_lock<std::mutex> 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<std::mutex> 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);
}
}