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);
}
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <fstream>
#include <mutex>
#include <string>
#include <thread>
namespace RenderCadenceCompositor
{
enum class LogLevel
{
Log,
Warning,
Error
};
struct LoggerConfig
{
LogLevel minimumLevel = LogLevel::Log;
bool writeToConsole = true;
bool writeToDebugOutput = true;
bool writeToFile = true;
std::string filePath = "logs/render-cadence-compositor.log";
std::size_t maxQueuedMessages = 1024;
};
struct LoggerCounters
{
uint64_t queued = 0;
uint64_t written = 0;
uint64_t dropped = 0;
};
const char* LogLevelName(LogLevel level);
class Logger
{
public:
struct Record
{
std::chrono::system_clock::time_point timestamp;
std::thread::id threadId;
LogLevel level = LogLevel::Log;
std::string subsystem;
std::string message;
};
static Logger& Instance();
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
~Logger();
void Start(LoggerConfig config = LoggerConfig());
void Stop();
void Write(LogLevel level, const std::string& subsystem, const std::string& message);
bool TryWrite(LogLevel level, const std::string& subsystem, const std::string& message);
void Log(const std::string& subsystem, const std::string& message);
void Warning(const std::string& subsystem, const std::string& message);
void Error(const std::string& subsystem, const std::string& message);
LoggerCounters Counters() const;
bool IsRunning() const;
private:
Logger() = default;
bool ShouldWrite(LogLevel level) const;
bool Enqueue(Record record, bool block);
void ThreadMain();
void WriteRecord(const Record& record);
void OpenFileSink();
void CloseFileSink();
LoggerConfig mConfig;
std::ofstream mFile;
std::thread mThread;
mutable std::mutex mMutex;
std::condition_variable mCondition;
std::deque<Record> mQueue;
bool mStopping = false;
bool mRunning = false;
mutable std::mutex mCountersMutex;
LoggerCounters mCounters;
};
bool TryLog(LogLevel level, const std::string& subsystem, const std::string& message);
void Log(const std::string& subsystem, const std::string& message);
void LogWarning(const std::string& subsystem, const std::string& message);
void LogError(const std::string& subsystem, const std::string& message);
}