mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +00:00 
			
		
		
		
	Merge pull request #3568 from daniellimws/logging-backends
Logging: Add customizable backends
This commit is contained in:
		
						commit
						1b94f25e6c
					
				
					 17 changed files with 405 additions and 24 deletions
				
			
		|  | @ -257,7 +257,12 @@ int main(int argc, char** argv) { | ||||||
| 
 | 
 | ||||||
|     Log::Filter log_filter; |     Log::Filter log_filter; | ||||||
|     log_filter.ParseFilterString(Settings::values.log_filter); |     log_filter.ParseFilterString(Settings::values.log_filter); | ||||||
|     Log::SetFilter(&log_filter); |     Log::SetGlobalFilter(log_filter); | ||||||
|  | 
 | ||||||
|  |     Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); | ||||||
|  |     FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX)); | ||||||
|  |     Log::AddBackend( | ||||||
|  |         std::make_unique<Log::FileBackend>(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE)); | ||||||
| 
 | 
 | ||||||
|     // Apply the command line arguments
 |     // Apply the command line arguments
 | ||||||
|     Settings::values.gdbstub_port = gdb_port; |     Settings::values.gdbstub_port = gdb_port; | ||||||
|  |  | ||||||
|  | @ -28,6 +28,8 @@ add_executable(citra-qt | ||||||
|     configuration/configure_system.h |     configuration/configure_system.h | ||||||
|     configuration/configure_web.cpp |     configuration/configure_web.cpp | ||||||
|     configuration/configure_web.h |     configuration/configure_web.h | ||||||
|  |     debugger/console.h | ||||||
|  |     debugger/console.cpp | ||||||
|     debugger/graphics/graphics.cpp |     debugger/graphics/graphics.cpp | ||||||
|     debugger/graphics/graphics.h |     debugger/graphics/graphics.h | ||||||
|     debugger/graphics/graphics_breakpoint_observer.cpp |     debugger/graphics/graphics_breakpoint_observer.cpp | ||||||
|  | @ -165,6 +167,14 @@ if (APPLE) | ||||||
|     target_sources(citra-qt PRIVATE ${MACOSX_ICON}) |     target_sources(citra-qt PRIVATE ${MACOSX_ICON}) | ||||||
|     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE TRUE) |     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE TRUE) | ||||||
|     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) |     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) | ||||||
|  | elseif(WIN32) | ||||||
|  |     # compile as a win32 gui application instead of a console application | ||||||
|  |     target_link_libraries(citra-qt PRIVATE Qt5::WinMain) | ||||||
|  |     if(MSVC) | ||||||
|  |         set_target_properties(citra-qt PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") | ||||||
|  |     elseif(MINGW) | ||||||
|  |         set_target_properties(citra-qt PROPERTIES LINK_FLAGS_RELEASE "-mwindows") | ||||||
|  |     endif() | ||||||
| endif() | endif() | ||||||
| 
 | 
 | ||||||
| create_target_directory_groups(citra-qt) | create_target_directory_groups(citra-qt) | ||||||
|  |  | ||||||
|  | @ -231,6 +231,7 @@ void Config::ReadValues() { | ||||||
|     UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); |     UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); | ||||||
|     UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); |     UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); | ||||||
|     UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); |     UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); | ||||||
|  |     UISettings::values.show_console = qt_config->value("showConsole", false).toBool(); | ||||||
| 
 | 
 | ||||||
|     qt_config->beginGroup("Multiplayer"); |     qt_config->beginGroup("Multiplayer"); | ||||||
|     UISettings::values.nickname = qt_config->value("nickname", "").toString(); |     UISettings::values.nickname = qt_config->value("nickname", "").toString(); | ||||||
|  | @ -391,6 +392,7 @@ void Config::SaveValues() { | ||||||
|     qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); |     qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); | ||||||
|     qt_config->setValue("firstStart", UISettings::values.first_start); |     qt_config->setValue("firstStart", UISettings::values.first_start); | ||||||
|     qt_config->setValue("calloutFlags", UISettings::values.callout_flags); |     qt_config->setValue("calloutFlags", UISettings::values.callout_flags); | ||||||
|  |     qt_config->setValue("showConsole", UISettings::values.show_console); | ||||||
| 
 | 
 | ||||||
|     qt_config->beginGroup("Multiplayer"); |     qt_config->beginGroup("Multiplayer"); | ||||||
|     qt_config->setValue("nickname", UISettings::values.nickname); |     qt_config->setValue("nickname", UISettings::values.nickname); | ||||||
|  |  | ||||||
|  | @ -2,13 +2,26 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #include <QDesktopServices> | ||||||
|  | #include <QUrl> | ||||||
| #include "citra_qt/configuration/configure_debug.h" | #include "citra_qt/configuration/configure_debug.h" | ||||||
|  | #include "citra_qt/debugger/console.h" | ||||||
|  | #include "citra_qt/ui_settings.h" | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/backend.h" | ||||||
|  | #include "common/logging/filter.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "ui_configure_debug.h" | #include "ui_configure_debug.h" | ||||||
| 
 | 
 | ||||||
| ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) { | ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) { | ||||||
|     ui->setupUi(this); |     ui->setupUi(this); | ||||||
|     this->setConfiguration(); |     this->setConfiguration(); | ||||||
|  |     connect(ui->open_log_button, &QPushButton::pressed, []() { | ||||||
|  |         QString path = QString::fromStdString(FileUtil::GetUserPath(D_LOGS_IDX)); | ||||||
|  |         QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||||
|  |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ConfigureDebug::~ConfigureDebug() {} | ConfigureDebug::~ConfigureDebug() {} | ||||||
|  | @ -17,11 +30,20 @@ void ConfigureDebug::setConfiguration() { | ||||||
|     ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); |     ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); | ||||||
|     ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); |     ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); | ||||||
|     ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); |     ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); | ||||||
|  |     ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | ||||||
|  |     ui->toggle_console->setChecked(UISettings::values.show_console); | ||||||
|  |     ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConfigureDebug::applyConfiguration() { | void ConfigureDebug::applyConfiguration() { | ||||||
|     Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); |     Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); | ||||||
|     Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); |     Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); | ||||||
|  |     UISettings::values.show_console = ui->toggle_console->isChecked(); | ||||||
|  |     Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); | ||||||
|  |     Debugger::ToggleConsole(); | ||||||
|  |     Log::Filter filter; | ||||||
|  |     filter.ParseFilterString(Settings::values.log_filter); | ||||||
|  |     Log::SetGlobalFilter(filter); | ||||||
|     Settings::Apply(); |     Settings::Apply(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -72,6 +72,47 @@ | ||||||
|      </item> |      </item> | ||||||
|     </layout> |     </layout> | ||||||
|    </item> |    </item> | ||||||
|  |    <item> | ||||||
|  |     <widget class="QGroupBox" name="groupBox_2"> | ||||||
|  |      <property name="title"> | ||||||
|  |       <string>Logging</string> | ||||||
|  |      </property> | ||||||
|  |      <layout class="QVBoxLayout" name="verticalLayout"> | ||||||
|  |       <item> | ||||||
|  |        <layout class="QHBoxLayout" name="horizontalLayout"> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QLabel" name="label"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Global Log Filter</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QLineEdit" name="log_filter_edit"/> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </item> | ||||||
|  |       <item> | ||||||
|  |        <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QCheckBox" name="toggle_console"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Show Log Console (Windows Only)</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QPushButton" name="open_log_button"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Open Log Location</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </item> | ||||||
|  |      </layout> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|    <item> |    <item> | ||||||
|     <spacer name="verticalSpacer"> |     <spacer name="verticalSpacer"> | ||||||
|      <property name="orientation"> |      <property name="orientation"> | ||||||
|  |  | ||||||
							
								
								
									
										39
									
								
								src/citra_qt/debugger/console.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/citra_qt/debugger/console.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | ||||||
|  | // Copyright 2018 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include <windows.h> | ||||||
|  | 
 | ||||||
|  | #include <wincon.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include "citra_qt/debugger/console.h" | ||||||
|  | #include "citra_qt/ui_settings.h" | ||||||
|  | #include "common/logging/backend.h" | ||||||
|  | 
 | ||||||
|  | namespace Debugger { | ||||||
|  | void ToggleConsole() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     FILE* temp; | ||||||
|  |     if (UISettings::values.show_console) { | ||||||
|  |         if (AllocConsole()) { | ||||||
|  |             // The first parameter for freopen_s is a out parameter, so we can just ignore it
 | ||||||
|  |             freopen_s(&temp, "CONIN$", "r", stdin); | ||||||
|  |             freopen_s(&temp, "CONOUT$", "w", stdout); | ||||||
|  |             freopen_s(&temp, "CONOUT$", "w", stderr); | ||||||
|  |             Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         if (FreeConsole()) { | ||||||
|  |             // In order to close the console, we have to also detach the streams on it.
 | ||||||
|  |             // Just redirect them to NUL if there is no console window
 | ||||||
|  |             Log::RemoveBackend(Log::ColorConsoleBackend::Name()); | ||||||
|  |             freopen_s(&temp, "NUL", "r", stdin); | ||||||
|  |             freopen_s(&temp, "NUL", "w", stdout); | ||||||
|  |             freopen_s(&temp, "NUL", "w", stderr); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | } // namespace Debugger
 | ||||||
							
								
								
									
										14
									
								
								src/citra_qt/debugger/console.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/citra_qt/debugger/console.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | // Copyright 2018 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | namespace Debugger { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Uses the WINAPI to hide or show the stderr console. This function is a placeholder until we can | ||||||
|  |  * get a real qt logging window which would work for all platforms. | ||||||
|  |  */ | ||||||
|  | void ToggleConsole(); | ||||||
|  | } // namespace Debugger
 | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
| #include "citra_qt/compatdb.h" | #include "citra_qt/compatdb.h" | ||||||
| #include "citra_qt/configuration/config.h" | #include "citra_qt/configuration/config.h" | ||||||
| #include "citra_qt/configuration/configure_dialog.h" | #include "citra_qt/configuration/configure_dialog.h" | ||||||
|  | #include "citra_qt/debugger/console.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics.h" | #include "citra_qt/debugger/graphics/graphics.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics_breakpoints.h" | #include "citra_qt/debugger/graphics/graphics_breakpoints.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics_cmdlists.h" | #include "citra_qt/debugger/graphics/graphics_cmdlists.h" | ||||||
|  | @ -35,6 +36,7 @@ | ||||||
| #include "citra_qt/ui_settings.h" | #include "citra_qt/ui_settings.h" | ||||||
| #include "citra_qt/updater/updater.h" | #include "citra_qt/updater/updater.h" | ||||||
| #include "citra_qt/util/clickable_label.h" | #include "citra_qt/util/clickable_label.h" | ||||||
|  | #include "common/common_paths.h" | ||||||
| #include "common/logging/backend.h" | #include "common/logging/backend.h" | ||||||
| #include "common/logging/filter.h" | #include "common/logging/filter.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | @ -387,6 +389,7 @@ void GMainWindow::RestoreUIState() { | ||||||
| 
 | 
 | ||||||
|     ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); |     ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); | ||||||
|     statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); |     statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); | ||||||
|  |     Debugger::ToggleConsole(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GMainWindow::ConnectWidgetEvents() { | void GMainWindow::ConnectWidgetEvents() { | ||||||
|  | @ -1335,8 +1338,7 @@ void GMainWindow::SyncMenuUISettings() { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| int main(int argc, char* argv[]) { | int main(int argc, char* argv[]) { | ||||||
|     Log::Filter log_filter(Log::Level::Info); |     Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); | ||||||
|     Log::SetFilter(&log_filter); |  | ||||||
| 
 | 
 | ||||||
|     MicroProfileOnThreadCreate("Frontend"); |     MicroProfileOnThreadCreate("Frontend"); | ||||||
|     SCOPE_EXIT({ MicroProfileShutdown(); }); |     SCOPE_EXIT({ MicroProfileShutdown(); }); | ||||||
|  | @ -1354,7 +1356,12 @@ int main(int argc, char* argv[]) { | ||||||
| 
 | 
 | ||||||
|     GMainWindow main_window; |     GMainWindow main_window; | ||||||
|     // After settings have been loaded by GMainWindow, apply the filter
 |     // After settings have been loaded by GMainWindow, apply the filter
 | ||||||
|  |     Log::Filter log_filter; | ||||||
|     log_filter.ParseFilterString(Settings::values.log_filter); |     log_filter.ParseFilterString(Settings::values.log_filter); | ||||||
|  |     Log::SetGlobalFilter(log_filter); | ||||||
|  |     FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX)); | ||||||
|  |     Log::AddBackend( | ||||||
|  |         std::make_unique<Log::FileBackend>(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE)); | ||||||
| 
 | 
 | ||||||
|     main_window.show(); |     main_window.show(); | ||||||
|     return app.exec(); |     return app.exec(); | ||||||
|  |  | ||||||
|  | @ -67,6 +67,9 @@ struct Values { | ||||||
|     QString room_port; |     QString room_port; | ||||||
|     uint host_type; |     uint host_type; | ||||||
|     qulonglong game_id; |     qulonglong game_id; | ||||||
|  | 
 | ||||||
|  |     // logging
 | ||||||
|  |     bool show_console; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| extern Values values; | extern Values values; | ||||||
|  |  | ||||||
|  | @ -36,8 +36,12 @@ | ||||||
| #define SDMC_DIR "sdmc" | #define SDMC_DIR "sdmc" | ||||||
| #define NAND_DIR "nand" | #define NAND_DIR "nand" | ||||||
| #define SYSDATA_DIR "sysdata" | #define SYSDATA_DIR "sysdata" | ||||||
|  | #define LOG_DIR "log" | ||||||
| 
 | 
 | ||||||
| // Filenames
 | // Filenames
 | ||||||
|  | // Files in the directory returned by GetUserPath(D_LOGS_IDX)
 | ||||||
|  | #define LOG_FILE "citra_log.txt" | ||||||
|  | 
 | ||||||
| // Files in the directory returned by GetUserPath(D_CONFIG_IDX)
 | // Files in the directory returned by GetUserPath(D_CONFIG_IDX)
 | ||||||
| #define EMU_CONFIG "emu.ini" | #define EMU_CONFIG "emu.ini" | ||||||
| #define DEBUGGER_CONFIG "debugger.ini" | #define DEBUGGER_CONFIG "debugger.ini" | ||||||
|  |  | ||||||
|  | @ -713,6 +713,8 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new | ||||||
|         paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; |         paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; | ||||||
|         paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; |         paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; | ||||||
|         paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; |         paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; | ||||||
|  |         // TODO: Put the logs in a better location for each OS
 | ||||||
|  |         paths[D_LOGS_IDX] = paths[D_USER_IDX] + LOG_DIR DIR_SEP; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!newPath.empty()) { |     if (!newPath.empty()) { | ||||||
|  | @ -799,8 +801,8 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam | ||||||
| 
 | 
 | ||||||
| IOFile::IOFile() {} | IOFile::IOFile() {} | ||||||
| 
 | 
 | ||||||
| IOFile::IOFile(const std::string& filename, const char openmode[]) { | IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { | ||||||
|     Open(filename, openmode); |     Open(filename, openmode, flags); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| IOFile::~IOFile() { | IOFile::~IOFile() { | ||||||
|  | @ -821,11 +823,16 @@ void IOFile::Swap(IOFile& other) { | ||||||
|     std::swap(m_good, other.m_good); |     std::swap(m_good, other.m_good); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IOFile::Open(const std::string& filename, const char openmode[]) { | bool IOFile::Open(const std::string& filename, const char openmode[], int flags) { | ||||||
|     Close(); |     Close(); | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|  |     if (flags != 0) { | ||||||
|  |         m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(), | ||||||
|  |                           Common::UTF8ToUTF16W(openmode).c_str(), flags); | ||||||
|  |     } else { | ||||||
|         _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), |         _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), | ||||||
|                   Common::UTF8ToUTF16W(openmode).c_str()); |                   Common::UTF8ToUTF16W(openmode).c_str()); | ||||||
|  |     } | ||||||
| #else | #else | ||||||
|     m_file = fopen(filename.c_str(), openmode); |     m_file = fopen(filename.c_str(), openmode); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -156,7 +156,11 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam | ||||||
| class IOFile : public NonCopyable { | class IOFile : public NonCopyable { | ||||||
| public: | public: | ||||||
|     IOFile(); |     IOFile(); | ||||||
|     IOFile(const std::string& filename, const char openmode[]); | 
 | ||||||
|  |     // flags is used for windows specific file open mode flags, which
 | ||||||
|  |     // allows citra to open the logs in shared write mode, so that the file
 | ||||||
|  |     // isn't considered "locked" while citra is open and people can open the log file and view it
 | ||||||
|  |     IOFile(const std::string& filename, const char openmode[], int flags = 0); | ||||||
| 
 | 
 | ||||||
|     ~IOFile(); |     ~IOFile(); | ||||||
| 
 | 
 | ||||||
|  | @ -165,7 +169,7 @@ public: | ||||||
| 
 | 
 | ||||||
|     void Swap(IOFile& other); |     void Swap(IOFile& other); | ||||||
| 
 | 
 | ||||||
|     bool Open(const std::string& filename, const char openmode[]); |     bool Open(const std::string& filename, const char openmode[], int flags = 0); | ||||||
|     bool Close(); |     bool Close(); | ||||||
| 
 | 
 | ||||||
|     template <typename T> |     template <typename T> | ||||||
|  | @ -224,6 +228,10 @@ public: | ||||||
|         return WriteArray(&object, 1); |         return WriteArray(&object, 1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     size_t WriteString(const std::string& str) { | ||||||
|  |         return WriteArray(str.c_str(), str.length()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     bool IsOpen() const { |     bool IsOpen() const { | ||||||
|         return nullptr != m_file; |         return nullptr != m_file; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -4,17 +4,144 @@ | ||||||
| 
 | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <array> | #include <array> | ||||||
|  | #include <chrono> | ||||||
|  | #include <condition_variable> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  | #include <memory> | ||||||
|  | #include <thread> | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include <share.h> // For _SH_DENYWR
 | ||||||
|  | #else | ||||||
|  | #define _SH_DENYWR 0 | ||||||
|  | #endif | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/common_funcs.h" // snprintf compatibility define
 | #include "common/common_funcs.h" // snprintf compatibility define
 | ||||||
| #include "common/logging/backend.h" | #include "common/logging/backend.h" | ||||||
| #include "common/logging/filter.h" |  | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/logging/text_formatter.h" | #include "common/logging/text_formatter.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
|  | #include "common/threadsafe_queue.h" | ||||||
| 
 | 
 | ||||||
| namespace Log { | namespace Log { | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Static state as a singleton. | ||||||
|  |  */ | ||||||
|  | class Impl { | ||||||
|  | public: | ||||||
|  |     static Impl& Instance() { | ||||||
|  |         static Impl backend; | ||||||
|  |         return backend; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Impl(Impl const&) = delete; | ||||||
|  |     const Impl& operator=(Impl const&) = delete; | ||||||
|  | 
 | ||||||
|  |     void PushEntry(Entry e) { | ||||||
|  |         std::lock_guard<std::mutex> lock(message_mutex); | ||||||
|  |         message_queue.Push(std::move(e)); | ||||||
|  |         message_cv.notify_one(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AddBackend(std::unique_ptr<Backend> backend) { | ||||||
|  |         std::lock_guard<std::mutex> lock(writing_mutex); | ||||||
|  |         backends.push_back(std::move(backend)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void RemoveBackend(const std::string& backend_name) { | ||||||
|  |         std::lock_guard<std::mutex> lock(writing_mutex); | ||||||
|  |         auto it = std::remove_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { | ||||||
|  |             return !strcmp(i->GetName(), backend_name.c_str()); | ||||||
|  |         }); | ||||||
|  |         backends.erase(it, backends.end()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const Filter& GetGlobalFilter() const { | ||||||
|  |         return filter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void SetGlobalFilter(const Filter& f) { | ||||||
|  |         filter = f; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Backend* GetBackend(const std::string& backend_name) { | ||||||
|  |         auto it = std::find_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { | ||||||
|  |             return !strcmp(i->GetName(), backend_name.c_str()); | ||||||
|  |         }); | ||||||
|  |         if (it == backends.end()) | ||||||
|  |             return nullptr; | ||||||
|  |         return it->get(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     Impl() { | ||||||
|  |         backend_thread = std::thread([&] { | ||||||
|  |             Entry entry; | ||||||
|  |             auto write_logs = [&](Entry& e) { | ||||||
|  |                 std::lock_guard<std::mutex> lock(writing_mutex); | ||||||
|  |                 for (const auto& backend : backends) { | ||||||
|  |                     backend->Write(e); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             while (true) { | ||||||
|  |                 std::unique_lock<std::mutex> lock(message_mutex); | ||||||
|  |                 message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); }); | ||||||
|  |                 if (!running) { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 write_logs(entry); | ||||||
|  |             } | ||||||
|  |             // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
 | ||||||
|  |             // where a system is repeatedly spamming logs even on close.
 | ||||||
|  |             constexpr int MAX_LOGS_TO_WRITE = 100; | ||||||
|  |             int logs_written = 0; | ||||||
|  |             while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) { | ||||||
|  |                 write_logs(entry); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ~Impl() { | ||||||
|  |         running = false; | ||||||
|  |         message_cv.notify_one(); | ||||||
|  |         backend_thread.join(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::atomic_bool running{true}; | ||||||
|  |     std::mutex message_mutex, writing_mutex; | ||||||
|  |     std::condition_variable message_cv; | ||||||
|  |     std::thread backend_thread; | ||||||
|  |     std::vector<std::unique_ptr<Backend>> backends; | ||||||
|  |     Common::MPSCQueue<Log::Entry> message_queue; | ||||||
|  |     Filter filter; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void ConsoleBackend::Write(const Entry& entry) { | ||||||
|  |     PrintMessage(entry); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ColorConsoleBackend::Write(const Entry& entry) { | ||||||
|  |     PrintColoredMessage(entry); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // _SH_DENYWR allows read only access to the file for other programs.
 | ||||||
|  | // It is #defined to 0 on other platforms
 | ||||||
|  | FileBackend::FileBackend(const std::string& filename) | ||||||
|  |     : file(filename, "w", _SH_DENYWR), bytes_written(0) {} | ||||||
|  | 
 | ||||||
|  | void FileBackend::Write(const Entry& entry) { | ||||||
|  |     // prevent logs from going over the maximum size (in case its spamming and the user doesn't
 | ||||||
|  |     // know)
 | ||||||
|  |     constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; | ||||||
|  |     if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     bytes_written += file.WriteString(FormatLogMessage(entry) + '\n'); | ||||||
|  |     if (entry.log_level >= Level::Error) { | ||||||
|  |         file.Flush(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
 | ||||||
| #define ALL_LOG_CLASSES()                                                                          \ | #define ALL_LOG_CLASSES()                                                                          \ | ||||||
|     CLS(Log)                                                                                       \ |     CLS(Log)                                                                                       \ | ||||||
|  | @ -132,15 +259,26 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign | ||||||
|     return entry; |     return entry; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static Filter* filter = nullptr; | void SetGlobalFilter(const Filter& filter) { | ||||||
|  |     Impl::Instance().SetGlobalFilter(filter); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void SetFilter(Filter* new_filter) { | void AddBackend(std::unique_ptr<Backend> backend) { | ||||||
|     filter = new_filter; |     Impl::Instance().AddBackend(std::move(backend)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void RemoveBackend(const std::string& backend_name) { | ||||||
|  |     Impl::Instance().RemoveBackend(backend_name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Backend* GetBackend(const std::string& backend_name) { | ||||||
|  |     return Impl::Instance().GetBackend(backend_name); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, | void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, | ||||||
|                 const char* function, const char* format, ...) { |                 const char* function, const char* format, ...) { | ||||||
|     if (filter && !filter->CheckMessage(log_class, log_level)) |     auto filter = Impl::Instance().GetGlobalFilter(); | ||||||
|  |     if (!filter.CheckMessage(log_class, log_level)) | ||||||
|         return; |         return; | ||||||
|     std::array<char, 4 * 1024> formatting_buffer; |     std::array<char, 4 * 1024> formatting_buffer; | ||||||
|     va_list args; |     va_list args; | ||||||
|  | @ -150,17 +288,19 @@ void LogMessage(Class log_class, Level log_level, const char* filename, unsigned | ||||||
|     Entry entry = CreateEntry(log_class, log_level, filename, line_num, function, |     Entry entry = CreateEntry(log_class, log_level, filename, line_num, function, | ||||||
|                               std::string(formatting_buffer.data())); |                               std::string(formatting_buffer.data())); | ||||||
| 
 | 
 | ||||||
|     PrintColoredMessage(entry); |     Impl::Instance().PushEntry(std::move(entry)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, | void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, | ||||||
|                        unsigned int line_num, const char* function, const char* format, |                        unsigned int line_num, const char* function, const char* format, | ||||||
|                        const fmt::format_args& args) { |                        const fmt::format_args& args) { | ||||||
|     if (filter && !filter->CheckMessage(log_class, log_level)) |     auto filter = Impl::Instance().GetGlobalFilter(); | ||||||
|  |     if (!filter.CheckMessage(log_class, log_level)) | ||||||
|         return; |         return; | ||||||
|  | 
 | ||||||
|     Entry entry = |     Entry entry = | ||||||
|         CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); |         CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); | ||||||
| 
 | 
 | ||||||
|     PrintColoredMessage(entry); |     Impl::Instance().PushEntry(std::move(entry)); | ||||||
| } | } | ||||||
| } // namespace Log
 | } // namespace Log
 | ||||||
|  |  | ||||||
|  | @ -6,8 +6,11 @@ | ||||||
| 
 | 
 | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <cstdarg> | #include <cstdarg> | ||||||
|  | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <utility> | #include <utility> | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/filter.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| 
 | 
 | ||||||
| namespace Log { | namespace Log { | ||||||
|  | @ -34,6 +37,80 @@ struct Entry { | ||||||
|     Entry& operator=(const Entry& o) = default; |     Entry& operator=(const Entry& o) = default; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Interface for logging backends. As loggers can be created and removed at runtime, this can be | ||||||
|  |  * used by a frontend for adding a custom logging backend as needed | ||||||
|  |  */ | ||||||
|  | class Backend { | ||||||
|  | public: | ||||||
|  |     virtual ~Backend() = default; | ||||||
|  |     virtual void SetFilter(const Filter& new_filter) { | ||||||
|  |         filter = new_filter; | ||||||
|  |     } | ||||||
|  |     virtual const char* GetName() const = 0; | ||||||
|  |     virtual void Write(const Entry& entry) = 0; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     Filter filter; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Backend that writes to stderr without any color commands | ||||||
|  |  */ | ||||||
|  | class ConsoleBackend : public Backend { | ||||||
|  | public: | ||||||
|  |     static const char* Name() { | ||||||
|  |         return "console"; | ||||||
|  |     } | ||||||
|  |     const char* GetName() const override { | ||||||
|  |         return Name(); | ||||||
|  |     } | ||||||
|  |     void Write(const Entry& entry) override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Backend that writes to stderr and with color | ||||||
|  |  */ | ||||||
|  | class ColorConsoleBackend : public Backend { | ||||||
|  | public: | ||||||
|  |     static const char* Name() { | ||||||
|  |         return "color_console"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const char* GetName() const override { | ||||||
|  |         return Name(); | ||||||
|  |     } | ||||||
|  |     void Write(const Entry& entry) override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Backend that writes to a file passed into the constructor | ||||||
|  |  */ | ||||||
|  | class FileBackend : public Backend { | ||||||
|  | public: | ||||||
|  |     explicit FileBackend(const std::string& filename); | ||||||
|  | 
 | ||||||
|  |     static const char* Name() { | ||||||
|  |         return "file"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const char* GetName() const override { | ||||||
|  |         return Name(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Write(const Entry& entry) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     FileUtil::IOFile file; | ||||||
|  |     size_t bytes_written; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void AddBackend(std::unique_ptr<Backend> backend); | ||||||
|  | 
 | ||||||
|  | void RemoveBackend(const std::string& backend_name); | ||||||
|  | 
 | ||||||
|  | Backend* GetBackend(const std::string& backend_name); | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Returns the name of the passed log class as a C-string. Subclasses are separated by periods |  * Returns the name of the passed log class as a C-string. Subclasses are separated by periods | ||||||
|  * instead of underscores as in the enumeration. |  * instead of underscores as in the enumeration. | ||||||
|  | @ -49,5 +126,10 @@ const char* GetLevelName(Level log_level); | ||||||
| Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | ||||||
|                   const char* function, std::string message); |                   const char* function, std::string message); | ||||||
| 
 | 
 | ||||||
| void SetFilter(Filter* filter); | /**
 | ||||||
|  |  * The global filter will prevent any messages from even being processed if they are filtered. Each | ||||||
|  |  * backend can have a filter, but if the level is lower than the global filter, the backend will | ||||||
|  |  * never get the message | ||||||
|  |  */ | ||||||
|  | void SetGlobalFilter(const Filter& filter); | ||||||
| } // namespace Log
 | } // namespace Log
 | ||||||
|  |  | ||||||
|  | @ -484,5 +484,4 @@ const char* TrimSourcePath(const char* path, const char* root) { | ||||||
|     } |     } | ||||||
|     return path; |     return path; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| } // namespace Common
 | } // namespace Common
 | ||||||
|  |  | ||||||
|  | @ -4,14 +4,13 @@ | ||||||
| 
 | 
 | ||||||
| #include "audio_core/dsp_interface.h" | #include "audio_core/dsp_interface.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
|  | #include "core/frontend/emu_window.h" | ||||||
| #include "core/gdbstub/gdbstub.h" | #include "core/gdbstub/gdbstub.h" | ||||||
| #include "core/hle/service/hid/hid.h" | #include "core/hle/service/hid/hid.h" | ||||||
| #include "core/hle/service/ir/ir.h" | #include "core/hle/service/ir/ir.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
| 
 | 
 | ||||||
| #include "core/frontend/emu_window.h" |  | ||||||
| 
 |  | ||||||
| namespace Settings { | namespace Settings { | ||||||
| 
 | 
 | ||||||
| Values values = {}; | Values values = {}; | ||||||
|  |  | ||||||
|  | @ -129,8 +129,6 @@ struct Values { | ||||||
|     float bg_green; |     float bg_green; | ||||||
|     float bg_blue; |     float bg_blue; | ||||||
| 
 | 
 | ||||||
|     std::string log_filter; |  | ||||||
| 
 |  | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     std::string sink_id; |     std::string sink_id; | ||||||
|     bool enable_audio_stretching; |     bool enable_audio_stretching; | ||||||
|  | @ -143,6 +141,7 @@ struct Values { | ||||||
|     // Debugging
 |     // Debugging
 | ||||||
|     bool use_gdbstub; |     bool use_gdbstub; | ||||||
|     u16 gdbstub_port; |     u16 gdbstub_port; | ||||||
|  |     std::string log_filter; | ||||||
| 
 | 
 | ||||||
|     // Movie
 |     // Movie
 | ||||||
|     std::string movie_play; |     std::string movie_play; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue