// Copyright (C) 2003 Dolphin Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official SVN repository and contact information can be found at // http://code.google.com/p/dolphin-emu/ #include "ppsspp_config.h" #if PPSSPP_PLATFORM(ANDROID) #include #endif #include #include #include "Common/Data/Encoding/Utf8.h" #include "Common/Log/LogManager.h" #if PPSSPP_PLATFORM(WINDOWS) #include "Common/Log/ConsoleListener.h" #endif #include "Common/Log/StdioListener.h" #include "Common/TimeUtil.h" #include "Common/Thread/ThreadUtil.h" #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" LogManager g_logManager; const char *hleCurrentThreadName = nullptr; bool *g_bLogEnabledSetting = nullptr; static const char level_to_char[8] = "-NEWIDV"; #if PPSSPP_PLATFORM(UWP) && defined(_DEBUG) #define LOG_MSC_OUTPUTDEBUG true #else #define LOG_MSC_OUTPUTDEBUG false #endif void GenericLog(LogLevel level, Log type, const char *file, int line, const char* fmt, ...) { if (g_bLogEnabledSetting && !(*g_bLogEnabledSetting)) return; va_list args; va_start(args, fmt); g_logManager.LogLine(level, type, file, line, fmt, args); va_end(args); } bool GenericLogEnabled(LogLevel level, Log type) { return (*g_bLogEnabledSetting) && g_logManager.IsEnabled(level, type); } // NOTE: Needs to be kept in sync with the Log enum. static const char * const g_logTypeNames[] = { "SYSTEM", "BOOT", "COMMON", "CPU", "FILESYS", "G3D", "HLE", "JIT", "LOADER", "ME", // Media Engine "MEMMAP", "SASMIX", "SAVESTATE", "FRAMEBUF", "AUDIO", "IO", "ACHIEVEMENTS", "HTTP", "PRINTF", "TEXREPLACE", "DEBUGGER", "SCEAUDIO", "SCECTRL", "SCEDISP", "SCEFONT", "SCEGE", "SCEINTC", "SCEIO", "SCEKERNEL", "SCEMODULE", "SCENET", "SCERTC", "SCESAS", "SCEUTIL", "SCEMISC", }; void LogManager::Init(bool *enabledSetting, bool headless) { g_bLogEnabledSetting = enabledSetting; if (initialized_) { // Just update the pointer, already done above. return; } initialized_ = true; _dbg_assert_(ARRAY_SIZE(g_logTypeNames) == (size_t)Log::NUMBER_OF_LOGS); for (size_t i = 0; i < ARRAY_SIZE(g_logTypeNames); i++) { truncate_cpy(log_[i].m_shortName, g_logTypeNames[i]); log_[i].enabled = true; #if defined(_DEBUG) log_[i].level = LogLevel::LDEBUG; #else log_[i].level = LogLevel::LINFO; #endif } // Remove file logging on small devices in Release mode. #if PPSSPP_PLATFORM(UWP) if (IsDebuggerPresent()) debuggerLog_ = new OutputDebugStringLogListener(); #else #if !defined(MOBILE_DEVICE) || defined(_DEBUG) fileLog_ = new FileLogListener(""); #if PPSSPP_PLATFORM(WINDOWS) #if !PPSSPP_PLATFORM(UWP) consoleLog_ = new ConsoleListener(); #endif if (IsDebuggerPresent()) debuggerLog_ = new OutputDebugStringLogListener(); #else stdioLog_ = new StdioListener(); #endif #endif ringLog_ = new RingbufferLogListener(); #endif #if !defined(MOBILE_DEVICE) || defined(_DEBUG) AddListener(fileLog_); #if PPSSPP_PLATFORM(WINDOWS) #if !PPSSPP_PLATFORM(UWP) AddListener(consoleLog_); #endif #else AddListener(stdioLog_); #endif #if defined(_MSC_VER) && (defined(USING_WIN_UI) || PPSSPP_PLATFORM(UWP)) if (IsDebuggerPresent() && debuggerLog_ && (LOG_MSC_OUTPUTDEBUG || headless)) AddListener(debuggerLog_); #endif AddListener(ringLog_); #endif } void LogManager::Shutdown() { if (!initialized_) { // already done return; } for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; ++i) { #if !defined(MOBILE_DEVICE) || defined(_DEBUG) RemoveListener(fileLog_); #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) RemoveListener(consoleLog_); #endif RemoveListener(stdioLog_); #if defined(_MSC_VER) && defined(USING_WIN_UI) RemoveListener(debuggerLog_); #endif #endif } // Make sure we don't shutdown while logging. RemoveListener locks too, but there are gaps. std::lock_guard listeners_lock(listeners_lock_); delete fileLog_; #if !defined(MOBILE_DEVICE) || defined(_DEBUG) #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) delete consoleLog_; #endif delete stdioLog_; delete debuggerLog_; #endif delete ringLog_; initialized_ = false; } LogManager::LogManager() {} LogManager::~LogManager() { Shutdown(); } void LogManager::ChangeFileLog(const char *filename) { if (fileLog_) { RemoveListener(fileLog_); delete fileLog_; fileLog_ = nullptr; } if (filename) { fileLog_ = new FileLogListener(filename); AddListener(fileLog_); } } void LogManager::SaveConfig(Section *section) { for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; i++) { section->Set((std::string(log_[i].m_shortName) + "Enabled"), log_[i].enabled); section->Set((std::string(log_[i].m_shortName) + "Level"), (int)log_[i].level); } } void LogManager::LoadConfig(const Section *section, bool debugDefaults) { for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; i++) { bool enabled = false; int level = 0; section->Get((std::string(log_[i].m_shortName) + "Enabled"), &enabled, true); section->Get((std::string(log_[i].m_shortName) + "Level"), &level, (int)(debugDefaults ? LogLevel::LDEBUG : LogLevel::LERROR)); log_[i].enabled = enabled; log_[i].level = (LogLevel)level; } } void LogManager::LogLine(LogLevel level, Log type, const char *file, int line, const char *format, va_list args) { char msgBuf[1024]; if (!initialized_) { // Fall back to printf or direct android logger with a small buffer if the log manager hasn't been initialized yet. #if PPSSPP_PLATFORM(ANDROID) vsnprintf(msgBuf, sizeof(msgBuf), format, args); __android_log_print(ANDROID_LOG_INFO, "PPSSPP", "EARLY: %s", msgBuf); #elif _MSC_VER vsnprintf(msgBuf, sizeof(msgBuf), format, args); OutputDebugStringUTF8(msgBuf); #else vprintf(format, args); printf("\n"); #endif return; } const LogChannel &log = log_[(size_t)type]; if (level > log.level || !log.enabled) return; LogMessage message; message.level = level; message.log = log.m_shortName; #ifdef _WIN32 static const char sep = '\\'; #else static const char sep = '/'; #endif const char *fileshort = strrchr(file, sep); if (fileshort) { do --fileshort; while (fileshort > file && *fileshort != sep); if (fileshort != file) file = fileshort + 1; } const char *threadName; #if PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(MAC) const char *hostThreadName = GetCurrentThreadName(); if (hostThreadName && strcmp(hostThreadName, "EmuThread") != 0 || !hleCurrentThreadName) { // Use the host thread name. threadName = hostThreadName; } else { // Use the PSP HLE thread name. threadName = hleCurrentThreadName; } #else threadName = hleCurrentThreadName; #endif if (threadName) { snprintf(message.header, sizeof(message.header), "%-12.12s %c[%s]: %s:%d", threadName, level_to_char[(int)level], log.m_shortName, file, line); } else { snprintf(message.header, sizeof(message.header), "%s:%d %c[%s]:", file, line, level_to_char[(int)level], log.m_shortName); } GetCurrentTimeFormatted(message.timestamp); va_list args_copy; va_copy(args_copy, args); size_t neededBytes = vsnprintf(msgBuf, sizeof(msgBuf), format, args); message.msg.resize(neededBytes + 1); if (neededBytes > sizeof(msgBuf)) { // Needed more space? Re-run vsnprintf. vsnprintf(&message.msg[0], neededBytes + 1, format, args_copy); } else { memcpy(&message.msg[0], msgBuf, neededBytes); } message.msg[neededBytes] = '\n'; va_end(args_copy); std::lock_guard listeners_lock(listeners_lock_); for (auto &iter : listeners_) { iter->Log(message); } } bool LogManager::IsEnabled(LogLevel level, Log type) { LogChannel &log = log_[(size_t)type]; if (level > log.level || !log.enabled) return false; return true; } void LogManager::AddListener(LogListener *listener) { _dbg_assert_(initialized_); if (!listener) return; std::lock_guard lk(listeners_lock_); listeners_.push_back(listener); } void LogManager::RemoveListener(LogListener *listener) { _dbg_assert_(initialized_); if (!listener) return; std::lock_guard lk(listeners_lock_); auto iter = std::find(listeners_.begin(), listeners_.end(), listener); if (iter != listeners_.end()) listeners_.erase(iter); } FileLogListener::FileLogListener(const char *filename) { if (strlen(filename) > 0) { fp_ = File::OpenCFile(Path(std::string(filename)), "at"); } SetEnabled(fp_ != nullptr); } FileLogListener::~FileLogListener() { if (fp_) fclose(fp_); } void FileLogListener::Log(const LogMessage &message) { if (!IsEnabled() || !IsValid()) return; std::lock_guard lk(m_log_lock); fprintf(fp_, "%s %s %s", message.timestamp, message.header, message.msg.c_str()); fflush(fp_); } void OutputDebugStringLogListener::Log(const LogMessage &message) { char buffer[4096]; // We omit the timestamp for easy copy-paste-diffing. snprintf(buffer, sizeof(buffer), "%s %s", message.header, message.msg.c_str()); #if _MSC_VER OutputDebugStringUTF8(buffer); #endif } void RingbufferLogListener::Log(const LogMessage &message) { if (!enabled_) return; messages_[curMessage_] = message; curMessage_++; if (curMessage_ >= MAX_LOGS) curMessage_ -= MAX_LOGS; count_++; } #ifdef _WIN32 void OutputDebugStringUTF8(const char *p) { wchar_t *temp = new wchar_t[65536]; int len = std::min(16383*4, (int)strlen(p)); int size = (int)MultiByteToWideChar(CP_UTF8, 0, p, len, NULL, 0); MultiByteToWideChar(CP_UTF8, 0, p, len, temp, size); temp[size] = 0; OutputDebugString(temp); delete[] temp; } #else void OutputDebugStringUTF8(const char *p) { INFO_LOG(Log::System, "%s", p); } #endif