#include #include #include #include #include #include #include #include #include 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(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(src)); } } struct LoggerThreadData { // Logger thread stuff std::thread loggerThread; std::mutex logQueueMutex; std::condition_variable logQueueCv; std::deque 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 lk(logQueueMutex); logQueue.emplace_back(std::move(md)); } logQueueCv.notify_one(); } }; Unique threadData; void LoggerGlobalState::LoggerThread() { auto& self = The(); // Fancy thread names. pthread_setname_np(pthread_self(), "LoggerThread"); while(true) { std::unique_lock 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(); 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(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