284 lines
5.4 KiB
C++
284 lines
5.4 KiB
C++
#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);
|
|
}
|
|
}
|