ps4delta/code/shared/utl/logger/logger.cpp
2019-12-07 19:49:11 +01:00

197 lines
No EOL
4.4 KiB
C++

#include <mutex>
#include <thread>
#include <vector>
#include "logger.h"
#include "threadsafe_queue.h"
namespace utl
{
template <typename InIt>
bool ComparePartialString(InIt begin, InIt end, const char* other)
{
for (; begin != end && *other != '\0'; ++begin, ++other) {
if (*begin != *other) {
return false;
}
}
// Only return true if both strings finished at the same point
return (begin == end) == (*other == '\0');
}
const char* TrimSourcePath(const char* path, const char* root)
{
const char* p = path;
while (*p != '\0') {
const char* next_slash = p;
while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
++next_slash;
}
bool is_src = ComparePartialString(p, next_slash, root);
p = next_slash;
if (*p != '\0') {
++p;
}
if (is_src) {
path = p;
}
}
return path;
}
class LogRegistry
{
std::mutex writing_lock;
std::thread backend_thread;
std::vector<std::unique_ptr<logBase>> sinks;
Common::MPSCQueue<logEntry> pending;
std::chrono::steady_clock::time_point time_origin;
public:
LogRegistry(LogRegistry const&) = delete;
const LogRegistry& operator=(LogRegistry const&) = delete;
static LogRegistry& Instance()
{
static LogRegistry backend;
return backend;
}
LogRegistry()
{
time_origin = std::chrono::steady_clock::now();
backend_thread = std::thread([&] {
logEntry entry;
auto write_logs = [&](logEntry& e) {
std::lock_guard lock{ writing_lock };
for (const auto& sink : sinks) {
sink->write(e);
}
};
// work the queue
while (true) {
entry = pending.PopWait();
// do we need to drain?
if (entry.final_entry)
break;
write_logs(entry);
}
// drain the queue
// only writes up to MAX_LOGS
constexpr int MAX_LOGS_TO_WRITE = 100;
int logs_written = 0;
while (logs_written++ < MAX_LOGS_TO_WRITE && pending.Pop(entry)) {
write_logs(entry);
}
});
}
~LogRegistry()
{
logEntry entry;
entry.final_entry = true;
pending.Push(entry);
backend_thread.join();
}
void AddEntry(logLevel lvl, const char* filename, uint32_t line, const char* func, std::string msg)
{
using std::chrono::duration_cast;
using std::chrono::steady_clock;
logEntry entry{};
entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
entry.log_level = lvl;
entry.line_num = line;
entry.function = func;
entry.message = std::move(msg);
entry.filename = TrimSourcePath(filename, "code");
pending.Push(entry);
}
logBase *AddSink(std::unique_ptr<logBase> sink)
{
std::lock_guard lock{ writing_lock };
auto* poop = sink.get();
sinks.push_back(std::move(sink));
return poop;
}
void RemoveSink(std::string_view name)
{
std::lock_guard lock{ writing_lock };
const auto it = std::remove_if(sinks.begin(), sinks.end(),
[&name](const auto& i) { return name == i->getName(); });
sinks.erase(it, sinks.end());
}
logBase* GetSink(std::string_view name)
{
const auto it = std::find_if(sinks.begin(), sinks.end(),
[&name](const auto& i) { return name == i->getName(); });
if (it == sinks.end())
return nullptr;
return it->get();
}
};
const char* GetLevelName(logLevel log_level)
{
#define LVL(x) \
case logLevel::x: \
return #x
switch (log_level) {
LVL(Trace);
LVL(Debug);
LVL(Info);
LVL(Warning);
LVL(Error);
LVL(Critical);
//case LogLevel::Count:
//UNREACHABLE();
}
#undef LVL
return nullptr;
}
std::string formatLogEntry(const logEntry& entry)
{
uint32_t time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000);
uint32_t time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000);
const char* level_name = GetLevelName(entry.log_level);
return fmt::format("[{:4d}.{:06d}] <{}> {}:{}:{}: {}", time_seconds, time_fractional,
level_name, entry.filename, entry.function, entry.line_num,
entry.message);
}
logBase *addLogSink(std::unique_ptr<logBase> sink) {
return LogRegistry::Instance().AddSink(std::move(sink));
}
void formatLogMsg(logLevel lvl, const char* filename, uint32_t line, const char* func, const char* fmt, const fmt::format_args& args)
{
auto& reg = LogRegistry::Instance();
reg.AddEntry(lvl, filename, line, func, fmt::vformat(fmt, args));
}
logBase* getLogSink(std::string_view name)
{
auto& reg = LogRegistry::Instance();
return reg.GetSink(name);
}
}