logging
This commit is contained in:
283
apps/RenderCadenceCompositor/logging/Logger.cpp
Normal file
283
apps/RenderCadenceCompositor/logging/Logger.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
101
apps/RenderCadenceCompositor/logging/Logger.h
Normal file
101
apps/RenderCadenceCompositor/logging/Logger.h
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user