#include "stdafx.h" #include "rPlatform.h" #include "Log.h" #include "rMsgBox.h" #include #include #include "Thread.h" #include "File.h" using namespace Log; std::unique_ptr g_log_manager; u32 LogMessage::size() const { //1 byte for NULL terminator return (u32)(sizeof(LogMessage::size_type) + sizeof(LogType) + sizeof(LogSeverity) + sizeof(std::string::value_type) * mText.size() + 1); } void LogMessage::serialize(char *output) const { LogMessage::size_type size = this->size(); memcpy(output, &size, sizeof(LogMessage::size_type)); output += sizeof(LogMessage::size_type); memcpy(output, &mType, sizeof(LogType)); output += sizeof(LogType); memcpy(output, &mServerity, sizeof(LogSeverity)); output += sizeof(LogSeverity); memcpy(output, mText.c_str(), mText.size() ); output += sizeof(std::string::value_type)*mText.size(); *output = '\0'; } LogMessage LogMessage::deserialize(char *input, u32* size_out) { LogMessage msg; LogMessage::size_type msgSize = *(reinterpret_cast(input)); input += sizeof(LogMessage::size_type); msg.mType = *(reinterpret_cast(input)); input += sizeof(LogType); msg.mServerity = *(reinterpret_cast(input)); input += sizeof(LogSeverity); if (msgSize > 9000) { int wtf = 6; } msg.mText.append(input, msgSize - 1 - sizeof(LogSeverity) - sizeof(LogType)); if (size_out){(*size_out) = msgSize;} return msg; } LogChannel::LogChannel() : LogChannel("unknown") {} LogChannel::LogChannel(const std::string& name) : name(name) , mEnabled(true) , mLogLevel(LogSeverityWarning) {} void LogChannel::log(const LogMessage &msg) { std::lock_guard lock(mListenerLock); for (auto &listener : mListeners) { listener->log(msg); } } void LogChannel::addListener(std::shared_ptr listener) { std::lock_guard lock(mListenerLock); mListeners.insert(listener); } void LogChannel::removeListener(std::shared_ptr listener) { std::lock_guard lock(mListenerLock); mListeners.erase(listener); } struct CoutListener : LogListener { void log(const LogMessage &msg) { std::cerr << msg.mText << std::endl; } }; struct FileListener : LogListener { fs::file mFile; bool mPrependChannelName; FileListener(const std::string& name = _PRGNAME_, bool prependChannel = true) : mFile(rPlatform::getConfigDir() + name + ".log", o_write | o_create | o_trunc) , mPrependChannelName(prependChannel) { if (!mFile) { rMessageBox("Can't create log file! (" + name + ".log)", "Error", rICON_ERROR); } } void log(const LogMessage &msg) { std::string text = msg.mText; if (mPrependChannelName) { text.insert(0, gTypeNameTable[static_cast(msg.mType)].mName); if (msg.mType == Log::TTY) { text = fmt::escape(text); if (text[text.length() - 1] != '\n') { text += '\n'; } } } mFile.write(text.c_str(), text.size()); } }; LogManager::LogManager() #ifdef BUFFERED_LOGGING : mExiting(false), mLogConsumer() #endif { auto it = mChannels.begin(); std::shared_ptr listener(new FileListener()); for (const LogTypeName& name : gTypeNameTable) { it->name = name.mName; it->addListener(listener); it++; } std::shared_ptr TTYListener(new FileListener("TTY",false)); getChannel(TTY).addListener(TTYListener); #ifdef BUFFERED_LOGGING mLogConsumer = std::thread(&LogManager::consumeLog, this); #endif } LogManager::~LogManager() { #ifdef BUFFERED_LOGGING mExiting = true; mBufferReady.notify_all(); mLogConsumer.join(); } void LogManager::consumeLog() { std::unique_lock lock(mStatusMut); while (!mExiting) { mBufferReady.wait(lock); mBuffer.lockGet(); size_t size = mBuffer.size(); std::vector local_messages(size); mBuffer.popN(&local_messages.front(), size); mBuffer.unlockGet(); u32 cursor = 0; u32 removed = 0; while (cursor < size) { Log::LogMessage msg = Log::LogMessage::deserialize(local_messages.data() + cursor, &removed); cursor += removed; getChannel(msg.mType).log(msg); } } #endif } void LogManager::log(LogMessage msg) { //don't do any formatting changes or filtering to the TTY output since we //use the raw output to do diffs with the output of a real PS3 and some //programs write text in single bytes to the console if (msg.mType != TTY) { std::string prefix; switch (msg.mServerity) { case LogSeveritySuccess: prefix = "S "; break; case LogSeverityNotice: prefix = "! "; break; case LogSeverityWarning: prefix = "W "; break; case LogSeverityError: prefix = "E "; break; } if (NamedThreadBase* thr = GetCurrentNamedThread()) { prefix += "{" + thr->GetThreadName() + "} "; } msg.mText.insert(0, prefix); msg.mText.append(1,'\n'); } #ifdef BUFFERED_LOGGING size_t size = msg.size(); std::vector temp_buffer(size); msg.serialize(temp_buffer.data()); mBuffer.pushRange(temp_buffer.begin(), temp_buffer.end()); mBufferReady.notify_one(); #else mChannels[static_cast(msg.mType)].log(msg); #endif } void LogManager::addListener(std::shared_ptr listener) { for (auto& channel : mChannels) { channel.addListener(listener); } } void LogManager::removeListener(std::shared_ptr listener) { for (auto& channel : mChannels) { channel.removeListener(listener); } } LogManager& LogManager::getInstance() { if (!g_log_manager) { g_log_manager.reset(new LogManager()); } return *g_log_manager; } LogChannel &LogManager::getChannel(LogType type) { return mChannels[static_cast(type)]; } void log_message(Log::LogType type, Log::LogSeverity sev, const char* text) { log_message(type, sev, std::string(text)); } void log_message(Log::LogType type, Log::LogSeverity sev, std::string text) { if (g_log_manager) { // another msvc bug makes this not work, uncomment this when it's fixed //g_log_manager->log({logType, severity, text}); Log::LogMessage msg{ type, sev, std::move(text) }; g_log_manager->log(msg); } else { rMessageBox(text, sev == LogSeverityNotice ? "Notice" : sev == LogSeverityWarning ? "Warning" : sev == LogSeveritySuccess ? "Success" : sev == LogSeverityError ? "Error" : "Unknown", sev == LogSeverityNotice ? rICON_INFORMATION : sev == LogSeverityWarning ? rICON_EXCLAMATION : sev == LogSeverityError ? rICON_ERROR : rICON_INFORMATION); } }