SSX3LobbyServer/lib/base/logger.cpp

173 lines
4.8 KiB
C++

#include <base/assert.hpp>
#include <base/logger.hpp>
#include <base/types.hpp>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <iostream>
#include <mutex>
#include <thread>
namespace base {
namespace logger_detail {
static constexpr std::string_view SeverityToString(MessageSeverity sev) {
// This must match order of logger_detail::MessageSeverity.
const char* MessageSeverityStringTable[] = { "Debug", "Info", "Warn", "Error", "Fatal" };
return MessageSeverityStringTable[static_cast<std::size_t>(sev)];
}
constexpr static auto ChannelToString(ChannelId channelId) -> std::string_view {
MessageSource src;
MessageComponentSource comp;
ChannelIdToComponents(channelId, src, comp);
switch(src) {
case MessageSource::Main: {
switch(comp) {
case MessageComponentSource::Main_Global: return "Main::Global";
default: BASE_CHECK(false, "Invalid component source");
}
};
case MessageSource::Base: {
switch(comp) {
case MessageComponentSource::Base_Assertions: return "Base::Assert";
default: BASE_CHECK(false, "Invalid component source");
}
};
case MessageSource::Http: {
switch(comp) {
case MessageComponentSource::Http_Server: return "HTTP::Server";
case MessageComponentSource::Http_WebSocketClient: return "HTTP::WebSocketClient";
default: BASE_CHECK(false, "Invalid component source");
}
};
case MessageSource::Server: {
switch(comp) {
case MessageComponentSource::Server_Server: return "LobbyServer2::Server";
default: BASE_CHECK(false, "Invalid component source");
}
};
default: BASE_CHECK(false, "Invalid channel source {:04x}", static_cast<u16>(src));
}
}
struct LoggerThreadData {
// Logger thread stuff
std::thread loggerThread;
std::mutex logQueueMutex;
std::condition_variable logQueueCv;
std::deque<MessageData> logQueue;
bool logThreadShutdown = false;
// N.B: This is stored/cached here just because it really does
// *NOT* need to be in a hot path.
const std::chrono::time_zone* timeZone = std::chrono::current_zone();
bool ShouldUnblock() {
// Always unblock if the logger thread needs to be shut down.
if(logThreadShutdown)
return true;
return !logQueue.empty();
}
void PushMessage(MessageData&& md) {
{
std::unique_lock<std::mutex> lk(logQueueMutex);
logQueue.emplace_back(std::move(md));
}
logQueueCv.notify_one();
}
};
Unique<LoggerThreadData> threadData;
void LoggerGlobalState::LoggerThread() {
auto& self = The();
// Fancy thread names.
pthread_setname_np(pthread_self(), "LoggerThread");
while(true) {
std::unique_lock<std::mutex> lk(threadData->logQueueMutex);
if(threadData->logQueue.empty()) {
// Await for messages.
threadData->logQueueCv.wait(lk, []() { return threadData->ShouldUnblock(); });
}
// Flush the logger queue until there are no more messages.
while(!threadData->logQueue.empty()) {
self.OutputMessage(threadData->logQueue.back());
threadData->logQueue.pop_back();
}
// Shutdown if requested.
if(threadData->logThreadShutdown)
break;
}
}
LoggerGlobalState& LoggerGlobalState::The() {
static LoggerGlobalState storage;
return storage;
}
LoggerGlobalState::LoggerGlobalState() {
// Spawn the logger thread
threadData = std::make_unique<LoggerThreadData>();
threadData->loggerThread = std::thread(&LoggerGlobalState::LoggerThread);
}
LoggerGlobalState::~LoggerGlobalState() {
// Shut down the logger thread
threadData->logThreadShutdown = true;
threadData->logQueueCv.notify_all();
threadData->loggerThread.join();
}
void LoggerGlobalState::AttachSink(Sink& sink) {
sinks.push_back(&sink);
}
void LoggerGlobalState::OutputMessage(const MessageData& data) {
// give up early if no sinks are attached
if(sinks.empty())
return;
if(data.severity < logLevel)
return;
auto formattedLoggerMessage =
std::format("[{:%F %H:%M:%S}|{}|{}] {}", std::chrono::floor<std::chrono::milliseconds>(threadData->timeZone->to_local(data.time)),
SeverityToString(data.severity), ChannelToString(data.channelId), data.message);
for(auto sink : sinks)
sink->OutputMessage(formattedLoggerMessage);
}
} // namespace logger_detail
Logger& Logger::Global() {
static Logger globalLogger;
return globalLogger;
}
void Logger::VOut(MessageSeverity severity, std::string_view format, std::format_args args) {
logger_detail::MessageData data {
.time = std::chrono::system_clock::now(), .severity = severity, .channelId = channelId, .message = std::vformat(format, args)
};
// Push data into logger thread.
logger_detail::threadData->PushMessage(std::move(data));
}
} // namespace base