diff --git a/common/Console.cpp b/common/Console.cpp index 902497589c..a8f1b991cc 100644 --- a/common/Console.cpp +++ b/common/Console.cpp @@ -1,524 +1,500 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ -#include "common/Threading.h" -#include "common/TraceLog.h" +#include "common/Console.h" #include "common/Assertions.h" -#include "common/RedtapeWindows.h" // OutputDebugString +#include "common/FileSystem.h" +#include "common/SmallString.h" +#include "common/Timer.h" -using namespace Threading; +#include "fmt/format.h" -// thread-local console indentation setting. -static thread_local int conlog_Indent(0); +#include +#include -// thread-local console color storage. -static thread_local ConsoleColors conlog_Color(DefaultConsoleColor); - -#ifdef __POSIX__ +#ifdef _WIN32 +#include "common/RedtapeWindows.h" +#else #include - -static FILE* stdout_fp = stdout; - -static bool checkSupportsColor() -{ - if (!isatty(fileno(stdout_fp))) - return false; - char* term = getenv("TERM"); - if (!term || (0 == strcmp(term, "dumb"))) - return false; - return true; // Probably supports color -} - -static bool supports_color = checkSupportsColor(); - -void Console_SetStdout(FILE* fp) -{ - stdout_fp = fp; -} #endif -// This function re-assigns the console log writer(s) to the specified target. It makes sure -// to flush any contents from the buffered console log (which typically accumulates due to -// log suspension during log file/window re-init operations) into the new log. -// -// Important! Only Assert and Null console loggers are allowed during C++ startup init (when -// the program or DLL first loads). Other log targets rely on the static buffer and a -// threaded mutex lock, which are only valid after C++ initialization has finished. -void Console_SetActiveHandler(const IConsoleWriter& writer, FILE* flushfp) +using namespace std::string_view_literals; + +// Dummy objects, need to get rid of them... +ConsoleLogWriter Console; +ConsoleLogWriter DevCon; + +#ifdef _DEBUG +ConsoleLogWriter DbgConWriter; +#else +NullLogWriter DbgConWriter; +#endif + +#define TIMESTAMP_FORMAT_STRING "[{:10.4f}] " +#define TIMESTAMP_PRINTF_STRING "[%10.4f] " + +namespace Log { - pxAssertMsg( - (writer.WriteRaw != NULL) && (writer.DoWriteLn != NULL) && - (writer.Newline != NULL) && (writer.SetTitle != NULL) && - (writer.DoSetColor != NULL), - "Invalid IConsoleWriter object! All function pointer interfaces must be implemented."); + static void WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_view message); + static void WriteToDebug(LOGLEVEL level, ConsoleColors color, std::string_view message); + static void WriteToFile(LOGLEVEL level, ConsoleColors color, std::string_view message); - Console = writer; - DevConWriter = writer; + static void UpdateMaxLevel(); -#ifdef PCSX2_DEBUG - DbgCon = writer; + static void ExecuteCallbacks(LOGLEVEL level, ConsoleColors color, std::string_view message); + + static Common::Timer::Value s_start_timestamp = Common::Timer::GetCurrentValue(); + + static LOGLEVEL s_max_level = LOGLEVEL_NONE; + static LOGLEVEL s_console_level = LOGLEVEL_NONE; + static LOGLEVEL s_debug_level = LOGLEVEL_NONE; + static LOGLEVEL s_file_level = LOGLEVEL_NONE; + static LOGLEVEL s_host_level = LOGLEVEL_NONE; + static bool s_log_timestamps = true; + + static FileSystem::ManagedCFilePtr s_file_handle; + static std::string s_file_path; + static std::mutex s_file_mutex; + + static HostCallbackType s_host_callback; + +#ifdef _WIN32 + static HANDLE s_hConsoleStdIn = NULL; + static HANDLE s_hConsoleStdOut = NULL; + static HANDLE s_hConsoleStdErr = NULL; +#endif +} // namespace Log + +float Log::GetCurrentMessageTime() +{ + return static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_start_timestamp)); +} + +__ri void Log::WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_view message) +{ + static constexpr std::string_view s_ansi_color_codes[ConsoleColors_Count] = { + "\033[0m"sv, // default + "\033[30m\033[1m"sv, // black + "\033[32m"sv, // green + "\033[31m"sv, // red + "\033[34m"sv, // blue + "\033[35m"sv, // magenta + "\033[35m"sv, // orange (FIXME) + "\033[37m"sv, // gray + "\033[36m"sv, // cyan + "\033[33m"sv, // yellow + "\033[37m"sv, // white + "\033[30m\033[1m"sv, // strong black + "\033[31m\033[1m"sv, // strong red + "\033[32m\033[1m"sv, // strong green + "\033[34m\033[1m"sv, // strong blue + "\033[35m\033[1m"sv, // strong magenta + "\033[35m\033[1m"sv, // strong orange (FIXME) + "\033[37m\033[1m"sv, // strong gray + "\033[36m\033[1m"sv, // strong cyan + "\033[33m\033[1m"sv, // strong yellow + "\033[37m\033[1m"sv, // strong white + }; + + static constexpr size_t BUFFER_SIZE = 512; + + SmallStackString buffer; + buffer.reserve(32 + message.length()); + buffer.append(s_ansi_color_codes[color]); + + if (s_log_timestamps) + buffer.append_fmt(TIMESTAMP_FORMAT_STRING, Log::GetCurrentMessageTime()); + + buffer.append(message); + buffer.append('\n'); + +#ifdef _WIN32 + const HANDLE hOutput = (level <= LOGLEVEL_WARNING) ? s_hConsoleStdErr : s_hConsoleStdOut; + + // Convert to UTF-16 first so Unicode characters display correctly. NT is going to do it anyway... + wchar_t wbuf[BUFFER_SIZE]; + wchar_t* wmessage_buf = wbuf; + int wmessage_buflen = static_cast(std::size(wbuf) - 1); + if (buffer.length() >= std::size(wbuf)) + { + wmessage_buflen = static_cast(buffer.length()); + wmessage_buf = static_cast(std::malloc(static_cast(wmessage_buflen) * sizeof(wchar_t))); + if (!wmessage_buf) + return; + } + + const int wmessage_size = MultiByteToWideChar(CP_UTF8, 0, buffer.data(), static_cast(buffer.length()), wmessage_buf, wmessage_buflen); + if (wmessage_size <= 0) + goto cleanup; + + DWORD chars_written; + WriteConsoleW(hOutput, wmessage_buf, static_cast(wmessage_size), &chars_written, nullptr); + +cleanup: + if (wmessage_buf != wbuf) + std::free(wmessage_buf); +#else + const int fd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO; + write(fd, buffer.data(), buffer.length()); #endif } -// Writes text to the Visual Studio Output window (Microsoft Windows only). -// On all other platforms this pipes to Stdout instead. -static void MSW_OutputDebugString(const char* text) +bool Log::IsConsoleOutputEnabled() +{ + return (s_console_level > LOGLEVEL_NONE); +} + +void Log::SetConsoleOutputLevel(LOGLEVEL level) +{ + if (s_console_level == level) + return; + + const bool was_enabled = (s_console_level > LOGLEVEL_NONE); + const bool now_enabled = (level > LOGLEVEL_NONE); + s_console_level = level; + UpdateMaxLevel(); + + if (was_enabled == now_enabled) + return; + + // Worst that happens here is we write to a bad handle.. + +#if defined(_WIN32) + static constexpr auto enable_virtual_terminal_processing = [](HANDLE hConsole) { + DWORD old_mode; + if (!GetConsoleMode(hConsole, &old_mode)) + return; + + // already enabled? + if (old_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + return; + + SetConsoleMode(hConsole, old_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + }; + + // On windows, no console is allocated by default on a windows based application + static bool console_was_allocated = false; + static HANDLE old_stdin = NULL; + static HANDLE old_stdout = NULL; + static HANDLE old_stderr = NULL; + + if (now_enabled) + { + old_stdin = GetStdHandle(STD_INPUT_HANDLE); + old_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + old_stderr = GetStdHandle(STD_ERROR_HANDLE); + + if (!old_stdout) + { + // Attach to the parent console if we're running from a command window + if (!AttachConsole(ATTACH_PARENT_PROCESS) && !AllocConsole()) + return; + + s_hConsoleStdIn = GetStdHandle(STD_INPUT_HANDLE); + s_hConsoleStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + s_hConsoleStdErr = GetStdHandle(STD_ERROR_HANDLE); + + enable_virtual_terminal_processing(s_hConsoleStdOut); + enable_virtual_terminal_processing(s_hConsoleStdErr); + + std::FILE* fp; + freopen_s(&fp, "CONIN$", "r", stdin); + freopen_s(&fp, "CONOUT$", "w", stdout); + freopen_s(&fp, "CONOUT$", "w", stderr); + + console_was_allocated = true; + } + else + { + s_hConsoleStdIn = old_stdin; + s_hConsoleStdOut = old_stdout; + s_hConsoleStdErr = old_stderr; + } + } + else + { + if (console_was_allocated) + { + console_was_allocated = false; + + std::FILE* fp; + freopen_s(&fp, "NUL:", "w", stderr); + freopen_s(&fp, "NUL:", "w", stdout); + freopen_s(&fp, "NUL:", "w", stdin); + + SetStdHandle(STD_ERROR_HANDLE, old_stderr); + SetStdHandle(STD_OUTPUT_HANDLE, old_stdout); + SetStdHandle(STD_INPUT_HANDLE, old_stdin); + + s_hConsoleStdIn = NULL; + s_hConsoleStdOut = NULL; + s_hConsoleStdErr = NULL; + + FreeConsole(); + } + } +#endif +} + +__ri void Log::WriteToDebug(LOGLEVEL level, ConsoleColors color, std::string_view message) { #ifdef _WIN32 - static bool hasDebugger = IsDebuggerPresent(); - if (hasDebugger) - OutputDebugStringA(text); -#else - fputs(text, stdout_fp); - fflush(stdout_fp); -#endif -} + static constexpr size_t BUFFER_SIZE = 512; - -// -------------------------------------------------------------------------------------- -// ConsoleNull -// -------------------------------------------------------------------------------------- - -static void ConsoleNull_SetTitle(const char* title) {} -static void ConsoleNull_DoSetColor(ConsoleColors color) {} -static void ConsoleNull_Newline() {} -static void ConsoleNull_DoWrite(const char* fmt) {} -static void ConsoleNull_DoWriteLn(const char* fmt) {} - -const IConsoleWriter ConsoleWriter_Null = + // Convert to UTF-16 first so Unicode characters display correctly. NT is going to do it anyway... + wchar_t wbuf[BUFFER_SIZE]; + wchar_t* wmessage_buf = wbuf; + int wmessage_buflen = static_cast(std::size(wbuf) - 1); + if (message.length() >= std::size(wbuf)) { - ConsoleNull_DoWrite, - ConsoleNull_DoWriteLn, - ConsoleNull_DoSetColor, - - ConsoleNull_DoWrite, - ConsoleNull_Newline, - ConsoleNull_SetTitle, - - 0, // instance-level indentation (should always be 0) -}; - -// -------------------------------------------------------------------------------------- -// Console_Stdout -// -------------------------------------------------------------------------------------- - -#if defined(__POSIX__) -static __fi const char* GetLinuxConsoleColor(ConsoleColors color) -{ - switch (color) - { - case Color_Black: - case Color_StrongBlack: - return "\033[30m\033[1m"; - - case Color_Red: - return "\033[31m"; - case Color_StrongRed: - return "\033[31m\033[1m"; - - case Color_Green: - return "\033[32m"; - case Color_StrongGreen: - return "\033[32m\033[1m"; - - case Color_Yellow: - return "\033[33m"; - case Color_StrongYellow: - return "\033[33m\033[1m"; - - case Color_Blue: - return "\033[34m"; - case Color_StrongBlue: - return "\033[34m\033[1m"; - - // No orange, so use magenta. - case Color_Orange: - case Color_Magenta: - return "\033[35m"; - case Color_StrongOrange: - case Color_StrongMagenta: - return "\033[35m\033[1m"; - - case Color_Cyan: - return "\033[36m"; - case Color_StrongCyan: - return "\033[36m\033[1m"; - - // Use 'white' instead of grey. - case Color_Gray: - case Color_White: - return "\033[37m"; - case Color_StrongGray: - case Color_StrongWhite: - return "\033[37m\033[1m"; - - // On some other value being passed, clear any formatting. - case Color_Default: - default: - return "\033[0m"; + wmessage_buflen = static_cast(message.length()); + wmessage_buf = static_cast(std::malloc((static_cast(wmessage_buflen) + 2) * sizeof(wchar_t))); + if (!wmessage_buf) + return; } -} + + int wmessage_size = 0; + if (!message.empty()) [[likely]] + { + wmessage_size = MultiByteToWideChar(CP_UTF8, 0, message.data(), static_cast(message.length()), wmessage_buf, wmessage_buflen); + if (wmessage_size <= 0) + goto cleanup; + } + + wmessage_buf[wmessage_size++] = L'\n'; + wmessage_buf[wmessage_size++] = 0; + OutputDebugStringW(wmessage_buf); + +cleanup: + if (wmessage_buf != wbuf) + std::free(wmessage_buf); #endif - -// One possible default write action at startup and shutdown is to use the stdout. -static void ConsoleStdout_DoWrite(const char* fmt) -{ - MSW_OutputDebugString(fmt); } -// Default write action at startup and shutdown is to use the stdout. -static void ConsoleStdout_DoWriteLn(const char* fmt) +bool Log::IsDebugOutputEnabled() { - MSW_OutputDebugString(fmt); - MSW_OutputDebugString("\n"); + return (s_console_level > LOGLEVEL_NONE); } -static void ConsoleStdout_Newline() +void Log::SetDebugOutputLevel(LOGLEVEL level) { - MSW_OutputDebugString("\n"); + s_debug_level = level; + UpdateMaxLevel(); } -static void ConsoleStdout_DoSetColor(ConsoleColors color) +__ri void Log::WriteToFile(LOGLEVEL level, ConsoleColors color, std::string_view message) { -#if defined(__POSIX__) - if (!supports_color) + std::unique_lock lock(s_file_mutex); + if (!s_file_handle) [[unlikely]] return; - fprintf(stdout_fp, "\033[0m%s", GetLinuxConsoleColor(color)); - fflush(stdout_fp); -#endif -} -static void ConsoleStdout_SetTitle(const char* title) -{ -#if defined(__POSIX__) - if (supports_color) - fputs("\033]0;", stdout_fp); - fputs(title, stdout_fp); - if (supports_color) - fputs("\007", stdout_fp); -#endif -} - -const IConsoleWriter ConsoleWriter_Stdout = + if (!message.empty()) [[likely]] { - ConsoleStdout_DoWrite, // Writes without newlines go to buffer to avoid error log spam. - ConsoleStdout_DoWriteLn, - ConsoleStdout_DoSetColor, - - ConsoleNull_DoWrite, // writes from re-piped stdout are ignored here, lest we create infinite loop hell >_< - ConsoleStdout_Newline, - ConsoleStdout_SetTitle, - 0, // instance-level indentation (should always be 0) -}; - -// -------------------------------------------------------------------------------------- -// ConsoleAssert -// -------------------------------------------------------------------------------------- - -static void ConsoleAssert_DoWrite(const char* fmt) -{ - pxFailRel("Console class has not been initialized"); -} - -static void ConsoleAssert_DoWriteLn(const char* fmt) -{ - pxFailRel("Console class has not been initialized"); -} - -const IConsoleWriter ConsoleWriter_Assert = - { - ConsoleAssert_DoWrite, - ConsoleAssert_DoWriteLn, - ConsoleNull_DoSetColor, - - ConsoleNull_DoWrite, - ConsoleNull_Newline, - ConsoleNull_SetTitle, - - 0, // instance-level indentation (should always be 0) -}; - -// ===================================================================================================== -// IConsoleWriter (implementations) -// ===================================================================================================== -// (all non-virtual members that do common work and then pass the result through DoWrite -// or DoWriteLn) - -// Parameters: -// glob_indent - this parameter is used to specify a global indentation setting. It is used by -// WriteLn function, but defaults to 0 for Warning and Error calls. Local indentation always -// applies to all writes. -std::string IConsoleWriter::_addIndentation(const std::string& src, int glob_indent = 0) const -{ - const int indent = glob_indent + _imm_indentation; - - std::string indentStr; - for (int i = 0; i < indent; i++) - indentStr += '\t'; - - std::string result; - result.reserve(src.length() + 16 * indent); - result.append(indentStr); - result.append(src); - - std::string::size_type pos = result.find('\n'); - while (pos != std::string::npos) - { - result.insert(pos + 1, indentStr); - pos = result.find('\n', pos + 1); - } - - return result; -} - -// Sets the indentation to be applied to all WriteLn's. The indentation is added to the -// primary write, and to any newlines specified within the write. Note that this applies -// to calls to WriteLn *only* -- calls to Write bypass the indentation parser. -const IConsoleWriter& IConsoleWriter::SetIndent(int tabcount) const -{ - conlog_Indent += tabcount; - pxAssert(conlog_Indent >= 0); - return *this; -} - -IConsoleWriter IConsoleWriter::Indent(int tabcount) const -{ - IConsoleWriter retval = *this; - retval._imm_indentation = tabcount; - return retval; -} - -// Changes the active console color. -// This color will be unset by calls to colored text methods -// such as ErrorMsg and Notice. -const IConsoleWriter& IConsoleWriter::SetColor(ConsoleColors color) const -{ - // Ignore current color requests since, well, the current color is already set. ;) - if (color == Color_Current) - return *this; - - pxAssertMsg((color > Color_Current) && (color < ConsoleColors_Count), "Invalid ConsoleColor specified."); - - if (conlog_Color != color) - DoSetColor(conlog_Color = color); - - return *this; -} - -ConsoleColors IConsoleWriter::GetColor() const -{ - return conlog_Color; -} - -// Restores the console color to default (usually black, or low-intensity white if the console uses a black background) -const IConsoleWriter& IConsoleWriter::ClearColor() const -{ - if (conlog_Color != DefaultConsoleColor) - DoSetColor(conlog_Color = DefaultConsoleColor); - - return *this; -} - -// -------------------------------------------------------------------------------------- -// ASCII/UTF8 (char*) -// -------------------------------------------------------------------------------------- - -bool IConsoleWriter::FormatV(const char* fmt, va_list args) const -{ - // TODO: Make this less rubbish - if ((_imm_indentation + conlog_Indent) > 0) - { - DoWriteLn(_addIndentation(StringUtil::StdStringFromFormatV(fmt, args), conlog_Indent).c_str()); + if (s_log_timestamps) + { + std::fprintf(s_file_handle.get(), TIMESTAMP_PRINTF_STRING "%.*s\n", GetCurrentMessageTime(), + static_cast(message.size()), message.data()); + } + else + { + std::fprintf(s_file_handle.get(), "%.*s\n", static_cast(message.size()), message.data()); + } } else { - DoWriteLn(StringUtil::StdStringFromFormatV(fmt, args).c_str()); + if (s_log_timestamps) + { + std::fprintf(s_file_handle.get(), TIMESTAMP_PRINTF_STRING "\n", GetCurrentMessageTime()); + } + else + { + std::fputc('\n', s_file_handle.get()); + } } - return false; + std::fflush(s_file_handle.get()); } -bool IConsoleWriter::WriteLn(const char* fmt, ...) const +bool Log::IsFileOutputEnabled() { - va_list args; - va_start(args, fmt); - FormatV(fmt, args); - va_end(args); - - return false; + return (s_file_level > LOGLEVEL_NONE); } -bool IConsoleWriter::WriteLn(ConsoleColors color, const char* fmt, ...) const +bool Log::SetFileOutputLevel(LOGLEVEL level, std::string path) { - va_list args; - va_start(args, fmt); - ConsoleColorScope cs(color); - FormatV(fmt, args); - va_end(args); + std::unique_lock lock(s_file_mutex); - return false; -} - -bool IConsoleWriter::Error(const char* fmt, ...) const -{ - va_list args; - va_start(args, fmt); - ConsoleColorScope cs(Color_StrongRed); - FormatV(fmt, args); - va_end(args); - - return false; -} - -bool IConsoleWriter::Warning(const char* fmt, ...) const -{ - va_list args; - va_start(args, fmt); - ConsoleColorScope cs(Color_StrongOrange); - FormatV(fmt, args); - va_end(args); - - return false; -} - -bool IConsoleWriter::WriteLn(ConsoleColors color, const std::string& str) const -{ - ConsoleColorScope cs(color); - return WriteLn(str); -} - -bool IConsoleWriter::WriteLn(const std::string& str) const -{ - // TODO: Make this less rubbish - if ((_imm_indentation + conlog_Indent) > 0) + const bool was_enabled = (s_file_level > LOGLEVEL_NONE); + const bool new_enabled = (level > LOGLEVEL_NONE && !path.empty()); + if (was_enabled != new_enabled || (new_enabled && path == s_file_path)) { - DoWriteLn(_addIndentation(str, conlog_Indent).c_str()); + if (new_enabled) + { + if (!s_file_handle || s_file_path != path) + { + s_file_handle.reset(); + s_file_handle = FileSystem::OpenManagedCFile(path.c_str(), "wb"); + if (s_file_handle) + { + s_file_path = std::move(path); + } + else + { + s_file_path = {}; + + if (IsConsoleOutputEnabled()) + WriteToConsole(LOGLEVEL_ERROR, Color_StrongRed, TinyString::from_fmt("Failed to open log file '{}'", path)); + } + } + } + else + { + s_file_handle.reset(); + s_file_path = {}; + } + } + + s_file_level = s_file_handle ? level : LOGLEVEL_NONE; + return IsFileOutputEnabled(); +} + +std::FILE* Log::GetFileLogHandle() +{ + std::unique_lock lock(s_file_mutex); + return s_file_handle.get(); +} + +bool Log::IsHostOutputEnabled() +{ + return (s_host_level > LOGLEVEL_NONE); +} + +void Log::SetHostOutputLevel(LOGLEVEL level, HostCallbackType callback) +{ + s_host_callback = callback; + s_host_level = callback ? level : LOGLEVEL_NONE; + UpdateMaxLevel(); +} + +bool Log::AreTimestampsEnabled() +{ + return s_log_timestamps; +} + +void Log::SetTimestampsEnabled(bool enabled) +{ + s_log_timestamps = enabled; +} + +LOGLEVEL Log::GetMaxLevel() +{ + return s_max_level; +} + +__ri void Log::UpdateMaxLevel() +{ + s_max_level = std::max(s_console_level, std::max(s_debug_level, std::max(s_file_level, s_host_level))); +} + +void Log::ExecuteCallbacks(LOGLEVEL level, ConsoleColors color, std::string_view message) +{ + // TODO: Cache the message time. + + // Split newlines into separate messages. + std::string_view::size_type start_pos = 0; + if (std::string_view::size_type end_pos = message.find('\n'); end_pos != std::string::npos) [[unlikely]] + { + for (;;) + { + std::string_view message_line; + if (start_pos != end_pos) + message_line = message.substr(start_pos, (end_pos == std::string_view::npos) ? end_pos : end_pos - start_pos); + + ExecuteCallbacks(level, color, message_line); + + if (end_pos == std::string_view::npos) + return; + + start_pos = end_pos + 1; + end_pos = message.find('\n', start_pos); + } + return; + } + + pxAssert(level > LOGLEVEL_NONE); + if (level <= s_console_level) + WriteToConsole(level, color, message); + + if (level <= s_debug_level) + WriteToDebug(level, color, message); + + if (level <= s_file_level) + WriteToFile(level, color, message); + + if (level <= s_host_level) + { + // double check in case of race here + const HostCallbackType callback = s_host_callback; + if (callback) + s_host_callback(level, color, message); + } +} + +void Log::Write(LOGLEVEL level, ConsoleColors color, std::string_view message) +{ + if (level > s_max_level) + return; + + ExecuteCallbacks(level, color, message); +} + +void Log::Writef(LOGLEVEL level, ConsoleColors color, const char* format, ...) +{ + std::va_list ap; + va_start(ap, format); + Writev(level, color, format, ap); + va_end(ap); +} + +void Log::Writev(LOGLEVEL level, ConsoleColors color, const char* format, va_list ap) +{ + if (level > s_max_level) + return; + + std::va_list ap_copy; + va_copy(ap_copy, ap); + +#ifdef _WIN32 + const u32 required_size = static_cast(_vscprintf(format, ap_copy)); +#else + const u32 required_size = std::vsnprintf(nullptr, 0, format, ap_copy); +#endif + va_end(ap_copy); + + if (required_size < 512) + { + char buffer[512]; + const int len = std::vsnprintf(buffer, std::size(buffer), format, ap); + if (len > 0) + ExecuteCallbacks(level, color, std::string_view(buffer, static_cast(len))); } else { - DoWriteLn(str.c_str()); - } - - return false; -} - -bool IConsoleWriter::Error(const std::string& str) const -{ - return WriteLn(Color_StrongRed, str); -} - -bool IConsoleWriter::Warning(const std::string& str) const -{ - return WriteLn(Color_StrongOrange, str); -} - -// -------------------------------------------------------------------------------------- -// ConsoleColorScope / ConsoleIndentScope -// -------------------------------------------------------------------------------------- - -ConsoleColorScope::ConsoleColorScope(ConsoleColors newcolor) -{ - m_IsScoped = false; - m_newcolor = newcolor; - EnterScope(); -} - -ConsoleColorScope::~ConsoleColorScope() -{ - LeaveScope(); -} - -void ConsoleColorScope::EnterScope() -{ - if (!m_IsScoped) - { - m_old_color = Console.GetColor(); - Console.SetColor(m_newcolor); - m_IsScoped = true; + char* buffer = new char[required_size + 1]; + const int len = std::vsnprintf(buffer, required_size + 1, format, ap); + if (len > 0) + ExecuteCallbacks(level, color, std::string_view(buffer, static_cast(len))); + delete[] buffer; } } -void ConsoleColorScope::LeaveScope() +void Log::WriteFmtArgs(LOGLEVEL level, ConsoleColors color, fmt::string_view fmt, fmt::format_args args) { - m_IsScoped = m_IsScoped && (Console.SetColor(m_old_color), false); -} - -ConsoleIndentScope::ConsoleIndentScope(int tabs) -{ - m_IsScoped = false; - m_amount = tabs; - EnterScope(); -} - -ConsoleIndentScope::~ConsoleIndentScope() -{ - LeaveScope(); -} - -void ConsoleIndentScope::EnterScope() -{ - m_IsScoped = m_IsScoped || (Console.SetIndent(m_amount), true); -} - -void ConsoleIndentScope::LeaveScope() -{ - m_IsScoped = m_IsScoped && (Console.SetIndent(-m_amount), false); -} - - -ConsoleAttrScope::ConsoleAttrScope(ConsoleColors newcolor, int indent) -{ - m_old_color = Console.GetColor(); - Console.SetIndent(m_tabsize = indent); - Console.SetColor(newcolor); -} - -ConsoleAttrScope::~ConsoleAttrScope() -{ - Console.SetColor(m_old_color); - Console.SetIndent(-m_tabsize); -} - - -// -------------------------------------------------------------------------------------- -// Default Writer for C++ init / startup: -// -------------------------------------------------------------------------------------- -// Currently all build types default to Stdout, which is very functional on Linux but not -// always so useful on Windows (which itself lacks a proper stdout console without using -// platform specific code). Under windows Stdout will attempt to write to the IDE Debug -// console, if one is available (such as running pcsx2 via MSVC). If not available, then -// the log message will pretty much be lost into the ether. -// -#define _DefaultWriter_ ConsoleWriter_Stdout - -IConsoleWriter Console = _DefaultWriter_; -IConsoleWriter DevConWriter = _DefaultWriter_; -bool DevConWriterEnabled = false; - -#ifdef PCSX2_DEBUG -IConsoleWriter DbgConWriter = _DefaultWriter_; -#endif - -NullConsoleWriter NullCon = {}; - -// -------------------------------------------------------------------------------------- -// ConsoleLogSource (implementations) -// -------------------------------------------------------------------------------------- - -// Writes to the console using the specified color. This overrides the default color setting -// for this log. -bool ConsoleLogSource::WriteV(ConsoleColors color, const char* fmt, va_list list) const -{ - ConsoleColorScope cs(color); - Console.WriteLn(StringUtil::StdStringFromFormatV(fmt, list)); - return false; -} - -// Writes to the console using the source's default color. Note that the source's default -// color will always be used, thus ConsoleColorScope() will not be effectual unless the -// console's default color is Color_Default. -bool ConsoleLogSource::WriteV(const char* fmt, va_list list) const -{ - WriteV(DefaultColor, fmt, list); - return false; + if (level > s_max_level) + return; + + fmt::memory_buffer buffer; + fmt::vformat_to(std::back_inserter(buffer), fmt, args); + + ExecuteCallbacks(level, color, std::string_view(buffer.data(), buffer.size())); } diff --git a/common/Console.h b/common/Console.h index 7625e0e88f..250007ab06 100644 --- a/common/Console.h +++ b/common/Console.h @@ -1,16 +1,19 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once #include "Pcsx2Defs.h" +#include "fmt/core.h" + +#include #include +// TODO: This whole thing needs to get ripped out. + enum ConsoleColors { - Color_Current = -1, - Color_Default = 0, Color_Black, @@ -42,191 +45,145 @@ enum ConsoleColors ConsoleColors_Count }; -static const ConsoleColors DefaultConsoleColor = Color_Default; - - -// ---------------------------------------------------------------------------------------- -// IConsoleWriter -- For printing messages to the console. -// ---------------------------------------------------------------------------------------- -// General ConsoleWrite Threading Guideline: -// PCSX2 is a threaded environment and multiple threads can write to the console asynchronously. -// Individual calls to ConsoleWriter APIs will be written in atomic fashion, however "partial" -// logs may end up interrupted by logs on other threads. This is usually not a problem for -// WriteLn, but may be undesirable for typical uses of Write. In cases where you want to -// print multi-line blocks of uninterrupted logs, compound the entire log into a single large -// string and issue that to WriteLn. -// -struct IConsoleWriter +enum LOGLEVEL { - // A direct console write, without tabbing or newlines. Useful to devs who want to do quick - // logging of various junk; but should *not* be used in production code due. - void(* WriteRaw)(const char* fmt); + LOGLEVEL_NONE, // Silences all log traffic + LOGLEVEL_ERROR, + LOGLEVEL_WARNING, + LOGLEVEL_INFO, + LOGLEVEL_DEV, + LOGLEVEL_DEBUG, + LOGLEVEL_TRACE, - // WriteLn implementation for internal use only. Bypasses tabbing, prefixing, and other - // formatting. - void(* DoWriteLn)(const char* fmt); - - // SetColor implementation for internal use only. - void(* DoSetColor)(ConsoleColors color); - - // Special implementation of DoWrite that's pretty much for MSVC use only. - // All implementations should map to DoWrite, except Stdio which should map to Null. - // (This avoids circular/recursive stdio output) - void(* DoWriteFromStdout)(const char* fmt); - - void(* Newline)(); - void(* SetTitle)(const char* title); - - // internal value for indentation of individual lines. Use the Indent() member to invoke. - int _imm_indentation; - - // For internal use only. - std::string _addIndentation(const std::string& src, int glob_indent) const; - - // ---------------------------------------------------------------------------- - // Public members; call these to print stuff to console! - // - // All functions always return false. Return value is provided only so that we can easily - // disable logs at compile time using the "0&&action" macro trick. - - ConsoleColors GetColor() const; - const IConsoleWriter& SetColor(ConsoleColors color) const; - const IConsoleWriter& ClearColor() const; - const IConsoleWriter& SetIndent(int tabcount = 1) const; - - IConsoleWriter Indent(int tabcount = 1) const; - - bool FormatV(const char* fmt, va_list args) const; - bool WriteLn(ConsoleColors color, const char* fmt, ...) const; - bool WriteLn(const char* fmt, ...) const; - bool Error(const char* fmt, ...) const; - bool Warning(const char* fmt, ...) const; - - bool WriteLn(ConsoleColors color, const std::string& str) const; - bool WriteLn(const std::string& str) const; - bool Error(const std::string& str) const; - bool Warning(const std::string& str) const; + LOGLEVEL_COUNT, }; -// -------------------------------------------------------------------------------------- -// NullConsoleWriter -// -------------------------------------------------------------------------------------- -// Used by Release builds for Debug and Devel writes (DbgCon / DevCon). Inlines to NOPs. :) -// -struct NullConsoleWriter +// TODO: Move this elsewhere, add channels. + +namespace Log { - void WriteRaw(const char* fmt) {} - void DoWriteLn(const char* fmt) {} - void DoSetColor(ConsoleColors color) {} - void DoWriteFromStdout(const char* fmt) {} - void Newline() {} - void SetTitle(const char* title) {} + // log message callback type + using HostCallbackType = void (*)(LOGLEVEL level, ConsoleColors color, std::string_view message); + // returns the time in seconds since the start of the process + float GetCurrentMessageTime(); - ConsoleColors GetColor() const { return Color_Current; } - const NullConsoleWriter& SetColor(ConsoleColors color) const { return *this; } - const NullConsoleWriter& ClearColor() const { return *this; } - const NullConsoleWriter& SetIndent(int tabcount = 1) const { return *this; } + // adds a standard console output + bool IsConsoleOutputEnabled(); + void SetConsoleOutputLevel(LOGLEVEL level); - NullConsoleWriter Indent(int tabcount = 1) const { return NullConsoleWriter(); } + // adds a debug console output + bool IsDebugOutputEnabled(); + void SetDebugOutputLevel(LOGLEVEL level); - bool FormatV(const char* fmt, va_list args) const { return false; } - bool WriteLn(ConsoleColors color, const char* fmt, ...) const { return false; } - bool WriteLn(const char* fmt, ...) const { return false; } - bool Error(const char* fmt, ...) const { return false; } - bool Warning(const char* fmt, ...) const { return false; } + // adds a file output + bool IsFileOutputEnabled(); + bool SetFileOutputLevel(LOGLEVEL level, std::string path); + + // returns the log file, this is really dangerous to use if it changes... + std::FILE* GetFileLogHandle(); + + // adds host output + bool IsHostOutputEnabled(); + void SetHostOutputLevel(LOGLEVEL level, HostCallbackType callback); + + // sets logging timestamps + bool AreTimestampsEnabled(); + void SetTimestampsEnabled(bool enabled); + + // Returns the current global filtering level. + LOGLEVEL GetMaxLevel(); + + // writes a message to the log + void Write(LOGLEVEL level, ConsoleColors color, std::string_view message); + void Writef(LOGLEVEL level, ConsoleColors color, const char* format, ...); + void Writev(LOGLEVEL level, ConsoleColors color, const char* format, va_list ap); + void WriteFmtArgs(LOGLEVEL level, ConsoleColors color, fmt::string_view fmt, fmt::format_args args); + + template + __fi static void Write(LOGLEVEL level, ConsoleColors color, fmt::format_string fmt, T&&... args) + { + // Avoid arg packing if filtered. + if (level <= GetMaxLevel()) + return WriteFmtArgs(level, color, fmt, fmt::make_format_args(args...)); + } +} // namespace Log + +// Adapter classes to handle old code. +template +struct ConsoleLogWriter +{ + __fi static void Error(std::string_view str) { Log::Write(level, Color_StrongRed, str); } + __fi static void Warning(std::string_view str) { Log::Write(level, Color_StrongOrange, str); } + __fi static void WriteLn(std::string_view str) { Log::Write(level, Color_Default, str); } + __fi static void WriteLn(ConsoleColors color, std::string_view str) { Log::Write(level, color, str); } + __fi static void WriteLn() { Log::Write(level, Color_Default, std::string_view()); } + __fi static void FormatV(const char* format, va_list ap) { Log::Writev(level, Color_Default, format, ap); } + __fi static void FormatV(ConsoleColors color, const char* format, va_list ap) { Log::Writev(level, color, format, ap); } + +#define MAKE_PRINTF_CONSOLE_WRITER(color) \ + do \ + { \ + std::va_list ap; \ + va_start(ap, format); \ + Log::Writev(level, color, format, ap); \ + va_end(ap); \ + } while (0) + + // clang-format off + __fi static void Error(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_StrongRed); } + __fi static void Warning(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_StrongOrange); } + __fi static void WriteLn(const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(Color_Default); } + __fi static void WriteLn(ConsoleColors color, const char* format, ...) { MAKE_PRINTF_CONSOLE_WRITER(color); } + // clang-format on + +#undef MAKE_PRINTF_CONSOLE_WRITER + +#define MAKE_FMT_CONSOLE_WRITER(color) do \ + { \ + if (level <= Log::GetMaxLevel()) \ + Log::WriteFmtArgs(level, color, fmt, fmt::make_format_args(args...)); \ +} \ + while (0) + + // clang-format off + template __fi static void ErrorFmt(fmt::format_string fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_StrongRed); } + template __fi static void WarningFmt(fmt::format_string fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_StrongOrange); } + template __fi static void WriteLnFmt(fmt::format_string fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(Color_Default); } + template __fi static void WriteLnFmt(ConsoleColors color, fmt::format_string fmt, T&&... args) { MAKE_FMT_CONSOLE_WRITER(color); } + // clang-format on + +#undef MAKE_FMT_CONSOLE_WRITER }; -// -------------------------------------------------------------------------------------- -// ConsoleIndentScope -// -------------------------------------------------------------------------------------- -// Provides a scoped indentation of the IConsoleWriter interface for the current thread. -// Any console writes performed from this scope will be indented by the specified number -// of tab characters. -// -// Scoped Object Notes: Like most scoped objects, this is intended to be used as a stack -// or temporary object only. Using it in a situation where the object's lifespan out-lives -// a scope will almost certainly result in unintended /undefined side effects. -// -class ConsoleIndentScope +struct NullLogWriter { - DeclareNoncopyableObject(ConsoleIndentScope); + // clang-format off + __fi static bool Error(std::string_view str) { return false; } + __fi static bool Warning(std::string_view str) { return false; } + __fi static bool WriteLn(std::string_view str) { return false; } + __fi static bool WriteLn(ConsoleColors color, std::string_view str) { return false; } + __fi static bool WriteLn() { return false; } -protected: - int m_amount; - bool m_IsScoped; + __fi static bool Error(const char* format, ...) { return false; } + __fi static bool Warning(const char* format, ...) { return false; } + __fi static bool WriteLn(const char* format, ...) { return false; } + __fi static bool WriteLn(ConsoleColors color, const char* format, ...) { return false; } -public: - // Constructor: The specified number of tabs will be appended to the current indentation - // setting. The tabs will be unrolled when the object leaves scope or is destroyed. - ConsoleIndentScope(int tabs = 1); - virtual ~ConsoleIndentScope(); - void EnterScope(); - void LeaveScope(); + template __fi static bool ErrorFmt(fmt::format_string fmt, T&&... args) { return false; } + template __fi static bool WarningFmt(fmt::format_string fmt, T&&... args) { return false; } + template __fi static bool WriteLnFmt(fmt::format_string fmt, T&&... args) { return false; } + template __fi static bool WriteLnFmt(ConsoleColors color, fmt::format_string fmt, T&&... args) { return false; } + // clang-format on }; -// -------------------------------------------------------------------------------------- -// ConsoleColorScope -// -------------------------------------------------------------------------------------- -class ConsoleColorScope -{ - DeclareNoncopyableObject(ConsoleColorScope); +extern ConsoleLogWriter Console; +extern ConsoleLogWriter DevCon; -protected: - ConsoleColors m_newcolor; - ConsoleColors m_old_color; - bool m_IsScoped; - -public: - ConsoleColorScope(ConsoleColors newcolor); - virtual ~ConsoleColorScope(); - void EnterScope(); - void LeaveScope(); -}; - -// -------------------------------------------------------------------------------------- -// ConsoleAttrScope -// -------------------------------------------------------------------------------------- -// Applies both color and tab attributes in a single object constructor. -// -class ConsoleAttrScope -{ - DeclareNoncopyableObject(ConsoleAttrScope); - -protected: - ConsoleColors m_old_color; - int m_tabsize; - -public: - ConsoleAttrScope(ConsoleColors newcolor, int indent = 0); - virtual ~ConsoleAttrScope(); -}; - -extern IConsoleWriter Console; - -#if defined(__POSIX__) -extern void Console_SetStdout(FILE* fp); -#endif -extern void Console_SetActiveHandler(const IConsoleWriter& writer, FILE* flushfp = NULL); - -extern const IConsoleWriter ConsoleWriter_Null; -extern const IConsoleWriter ConsoleWriter_Stdout; -extern const IConsoleWriter ConsoleWriter_Assert; - -extern NullConsoleWriter NullCon; - -extern IConsoleWriter DevConWriter; -extern bool DevConWriterEnabled; - -#ifdef PCSX2_DEVBUILD -#define DevCon DevConWriter -#else -#define DevCon DevConWriterEnabled&& DevConWriter -#endif - -#ifdef PCSX2_DEBUG -extern IConsoleWriter DbgConWriter; +#ifdef _DEBUG +extern ConsoleLogWriter DbgConWriter; #define DbgCon DbgConWriter #else -#define DbgCon 0 && NullCon +extern NullLogWriter DbgConWriter; +#define DbgCon 0 && DbgConWriter #endif diff --git a/common/TraceLog.h b/common/TraceLog.h index 8dd39bb9f1..77832cef52 100644 --- a/common/TraceLog.h +++ b/common/TraceLog.h @@ -183,7 +183,15 @@ public: return false; } - bool WriteV(const char* fmt, va_list list) const; + bool WriteV(const char* fmt, va_list list) const + { + Console.FormatV(fmt, list); + return false; + } - bool WriteV(ConsoleColors color, const char* fmt, va_list list) const; + bool WriteV(ConsoleColors color, const char* fmt, va_list list) const + { + Console.FormatV(color, fmt, list); + return false; + } }; diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp index 9dd81da7c9..90e667d8b2 100644 --- a/pcsx2-gsrunner/Main.cpp +++ b/pcsx2-gsrunner/Main.cpp @@ -35,7 +35,6 @@ #include "pcsx2/INISettingsInterface.h" #include "pcsx2/ImGui/ImGuiManager.h" #include "pcsx2/Input/InputManager.h" -#include "pcsx2/LogSink.h" #include "pcsx2/MTGS.h" #include "pcsx2/SIO/Pad/Pad.h" #include "pcsx2/PerformanceMetrics.h" @@ -442,7 +441,7 @@ void GSRunner::InitializeConsole() const char* var = std::getenv("PCSX2_NOCONSOLE"); s_no_console = (var && StringUtil::FromChars(var).value_or(false)); if (!s_no_console) - LogSink::InitializeEarlyConsole(); + Log::SetConsoleOutputLevel(LOGLEVEL_DEBUG); } bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& params) @@ -568,7 +567,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa { // disable timestamps, since we want to be able to diff the logs Console.WriteLn("Logging to %s...", logfile); - LogSink::SetFileLogPath(logfile); + VMManager::Internal::SetFileLogPath(logfile); s_settings_interface.SetBoolValue("Logging", "EnableFileLogging", true); s_settings_interface.SetBoolValue("Logging", "EnableTimestamps", false); } @@ -698,7 +697,6 @@ int main(int argc, char* argv[]) VMManager::Internal::CPUThreadShutdown(); GSRunner::DestroyPlatformWindow(); - LogSink::CloseFileLog(); return EXIT_SUCCESS; } diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 2745dfe3eb..bd6d65f9a8 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -27,7 +27,6 @@ #include "pcsx2/GSDumpReplayer.h" #include "pcsx2/GameList.h" #include "pcsx2/Host.h" -#include "pcsx2/LogSink.h" #include "pcsx2/MTGS.h" #include "pcsx2/PerformanceMetrics.h" #include "pcsx2/Recording/InputRecording.h" diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 43b89622f6..45a1f622d0 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -26,7 +26,6 @@ #include "pcsx2/ImGui/ImGuiManager.h" #include "pcsx2/ImGui/ImGuiOverlays.h" #include "pcsx2/Input/InputManager.h" -#include "pcsx2/LogSink.h" #include "pcsx2/MTGS.h" #include "pcsx2/PerformanceMetrics.h" #include "pcsx2/SysForwardDefs.h" @@ -64,6 +63,7 @@ EmuThread* g_emu_thread = nullptr; ////////////////////////////////////////////////////////////////////////// namespace QtHost { + static void InitializeEarlyConsole(); static void PrintCommandLineVersion(); static void PrintCommandLineHelp(const std::string_view& progname); static std::shared_ptr& AutoBoot(std::shared_ptr& autoboot); @@ -1240,7 +1240,8 @@ bool QtHost::InitializeConfig() s_run_setup_wizard = s_run_setup_wizard || s_base_settings_interface->GetBoolValue("UI", "SetupWizardIncomplete", false); - LogSink::SetBlockSystemConsole(QtHost::InNoGUIMode()); + // TODO: -nogui console block? + VMManager::Internal::LoadStartupSettings(); InstallTranslator(nullptr); return true; @@ -1575,12 +1576,27 @@ static void SignalHandler(int signal) #endif } +#ifdef _WIN32 + +static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType) +{ + if (dwCtrlType != CTRL_C_EVENT) + return FALSE; + + SignalHandler(SIGTERM); + return TRUE; +} + +#endif + void QtHost::HookSignals() { std::signal(SIGINT, SignalHandler); std::signal(SIGTERM, SignalHandler); -#ifdef __linux__ +#if defined(_WIN32) + SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE); +#elif defined(__linux__) // Ignore SIGCHLD by default on Linux, since we kick off aplay asynchronously. struct sigaction sa_chld = {}; sigemptyset(&sa_chld.sa_mask); @@ -1589,9 +1605,14 @@ void QtHost::HookSignals() #endif } +void QtHost::InitializeEarlyConsole() +{ + Log::SetConsoleOutputLevel(LOGLEVEL_DEBUG); +} + void QtHost::PrintCommandLineVersion() { - LogSink::InitializeEarlyConsole(); + InitializeEarlyConsole(); std::fprintf(stderr, "%s\n", (GetAppNameAndVersion() + GetAppConfigSuffix()).toUtf8().constData()); std::fprintf(stderr, "https://pcsx2.net/\n"); std::fprintf(stderr, "\n"); @@ -1715,7 +1736,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptrtoStdString()); + VMManager::Internal::SetFileLogPath((++it)->toStdString()); continue; } else if (CHECK_ARG(QStringLiteral("-bios"))) @@ -1736,7 +1757,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptrsource_type.has_value() && autoboot->filename.empty() && autoboot->elf_override.empty()) { - LogSink::InitializeEarlyConsole(); Console.Warning("Skipping autoboot due to no boot parameters."); autoboot.reset(); } @@ -1956,8 +1976,5 @@ shutdown_and_exit: if (s_base_settings_interface->IsDirty()) s_base_settings_interface->Save(); - // Ensure emulog is flushed. - LogSink::CloseFileLog(); - return result; } diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp index f60e6693ff..623ba202c5 100644 --- a/pcsx2/CDVD/CDVD.cpp +++ b/pcsx2/CDVD/CDVD.cpp @@ -476,7 +476,7 @@ static CDVDDiscType GetPS2ElfName(IsoReader& isor, std::string* name, std::strin if (value.empty() && (lineno == (lines.size() - 1))) { Console.Warning("(SYSTEM.CNF) Unusual or malformed entry in SYSTEM.CNF ignored:"); - Console.Indent().WriteLn(std::string(line)); + Console.WarningFmt(" {}", line); continue; } diff --git a/pcsx2/CDVD/InputIsoFile.cpp b/pcsx2/CDVD/InputIsoFile.cpp index 98bea88d85..ef06f4b01d 100644 --- a/pcsx2/CDVD/InputIsoFile.cpp +++ b/pcsx2/CDVD/InputIsoFile.cpp @@ -260,14 +260,12 @@ bool InputIsoFile::Open(std::string srcfile, Error* error, bool testOnly) Console.WriteLn(Color_StrongBlue, "isoFile open ok: %s", m_filename.c_str()); - ConsoleIndentScope indent; - - Console.WriteLn("Image type = %s", nameFromType(m_type)); - //Console.WriteLn("Fileparts = %u", m_numparts); // Pointless print, it's 1 unless it says otherwise above - DevCon.WriteLn("blocks = %u", m_blocks); - DevCon.WriteLn("offset = %d", m_offset); - DevCon.WriteLn("blocksize = %u", m_blocksize); - DevCon.WriteLn("blockoffset = %d", m_blockofs); + Console.WriteLn(" Image type = %s", nameFromType(m_type)); + //Console.WriteLn(" Fileparts = %u", m_numparts); // Pointless print, it's 1 unless it says otherwise above + DevCon.WriteLn(" blocks = %u", m_blocks); + DevCon.WriteLn(" offset = %d", m_offset); + DevCon.WriteLn(" blocksize = %u", m_blocksize); + DevCon.WriteLn(" blockoffset = %d", m_blockofs); return true; } diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt index 168ae9e847..55b0b39a5d 100644 --- a/pcsx2/CMakeLists.txt +++ b/pcsx2/CMakeLists.txt @@ -77,7 +77,6 @@ set(pcsx2Sources HwRead.cpp HwWrite.cpp LayeredSettingsInterface.cpp - LogSink.cpp INISettingsInterface.cpp Interpreter.cpp IopBios.cpp @@ -169,7 +168,6 @@ set(pcsx2Headers IopHw.h IopMem.h LayeredSettingsInterface.h - LogSink.h PINE.h Mdec.h MTGS.h diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index 65989a92d2..47e8fbb401 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -417,7 +417,7 @@ void UpdateVSyncRate(bool force) Console.WriteLn(Color_Green, "(UpdateVSyncRate) Mode Changed to %s.", ReportVideoMode()); if (custom && video_mode_initialized) - Console.Indent().WriteLn(Color_StrongGreen, "... with user configured refresh rate: %.02f Hz", vertical_frequency); + Console.WriteLn(Color_StrongGreen, " ... with user configured refresh rate: %.02f Hz", vertical_frequency); hsyncCounter.CycleT = (hsyncCounter.Mode == MODE_HBLANK) ? vSyncInfo.hBlank : vSyncInfo.hRender; vsyncCounter.CycleT = (vsyncCounter.Mode == MODE_GSBLANK) ? diff --git a/pcsx2/DebugTools/Debug.h b/pcsx2/DebugTools/Debug.h index 64dac10168..ea2bfd1061 100644 --- a/pcsx2/DebugTools/Debug.h +++ b/pcsx2/DebugTools/Debug.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once @@ -7,8 +7,10 @@ #include "Config.h" #include "Memory.h" +#include + +// TODO: Purge emuLog and all this other nonsense, just go through Log with LOGLEVEL_TRACE. extern FILE *emuLog; -extern std::string emuLogName; extern char* disVU0MicroUF(u32 code, u32 pc); extern char* disVU0MicroLF(u32 code, u32 pc); @@ -214,23 +216,34 @@ class ConsoleLogFromVM : public BaseTraceLogSource public: ConsoleLogFromVM( const TraceLogDescriptor* desc ) : _parent( desc ) {} - bool Write( const char* msg ) const + bool Write(std::string_view msg) { - ConsoleColorScope cs(conColor); - Console.WriteRaw(msg); - - // Buffered output isn't compatible with the testsuite. The end of test - // doesn't always get flushed. Let's just flush all the output if EE/IOP - // print anything. - fflush(NULL); + for (const char ch : msg) + { + if (ch == '\n') + { + if (!m_buffer.empty()) + { + Console.WriteLn(conColor, m_buffer); + m_buffer.clear(); + } + } + else if (ch < 0x20) + { + // Ignore control characters. + // Otherwise you get fun bells going off. + } + else + { + m_buffer.push_back(ch); + } + } return false; } - bool Write(const std::string& msg) const - { - return Write(msg.c_str()); - } +private: + std::string m_buffer; }; // -------------------------------------------------------------------------------------- diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index a296294d2a..40ceeaed45 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -171,7 +171,7 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool GSConfig.OsdShowGPU = GSConfig.OsdShowGPU && g_gs_device->SetGPUTimingEnabled(true); Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(new_api)); - Console.Indent().WriteLn(g_gs_device->GetDriverInfo()); + Console.WriteLn(g_gs_device->GetDriverInfo()); return true; } diff --git a/pcsx2/IPU/IPU.cpp b/pcsx2/IPU/IPU.cpp index ec78a24871..93c28a24a9 100644 --- a/pcsx2/IPU/IPU.cpp +++ b/pcsx2/IPU/IPU.cpp @@ -115,7 +115,7 @@ void ReportIPU() Console.WriteLn("coded_block_pattern = 0x%x.", coded_block_pattern); Console.WriteLn("g_decoder = 0x%x.", &decoder); Console.WriteLn(ipu_cmd.desc()); - Console.Newline(); + Console.WriteLn(); } bool SaveStateBase::ipuFreeze() diff --git a/pcsx2/LogSink.cpp b/pcsx2/LogSink.cpp deleted file mode 100644 index 4a1ae3553a..0000000000 --- a/pcsx2/LogSink.cpp +++ /dev/null @@ -1,427 +0,0 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team -// SPDX-License-Identifier: LGPL-3.0+ - -#include "DebugTools/Debug.h" -#include "Host.h" -#include "LogSink.h" - -#include "common/Assertions.h" -#include "common/Console.h" -#include "common/FileSystem.h" -#include "common/Path.h" -#include "common/SettingsInterface.h" -#include "common/StringUtil.h" -#include "common/Timer.h" - -#ifdef _WIN32 -#include "common/RedtapeWindows.h" -#endif - -#include -#include "fmt/core.h" - -// Used on both Windows and Linux. -#ifdef _WIN32 -static const wchar_t s_console_colors[][ConsoleColors_Count] = { -#define CC(x) L##x -#else -static const char s_console_colors[][ConsoleColors_Count] = { -#define CC(x) x -#endif - CC("\033[0m"), // default - CC("\033[30m\033[1m"), // black - CC("\033[32m"), // green - CC("\033[31m"), // red - CC("\033[34m"), // blue - CC("\033[35m"), // magenta - CC("\033[35m"), // orange (FIXME) - CC("\033[37m"), // gray - CC("\033[36m"), // cyan - CC("\033[33m"), // yellow - CC("\033[37m"), // white - CC("\033[30m\033[1m"), // strong black - CC("\033[31m\033[1m"), // strong red - CC("\033[32m\033[1m"), // strong green - CC("\033[34m\033[1m"), // strong blue - CC("\033[35m\033[1m"), // strong magenta - CC("\033[35m\033[1m"), // strong orange (FIXME) - CC("\033[37m\033[1m"), // strong gray - CC("\033[36m\033[1m"), // strong cyan - CC("\033[33m\033[1m"), // strong yellow - CC("\033[37m\033[1m") // strong white -}; -#undef CC - -static bool s_block_system_console = false; -static Common::Timer::Value s_log_start_timestamp = Common::Timer::GetCurrentValue(); -static bool s_log_timestamps = false; -static std::mutex s_log_mutex; - -// Replacement for Console so we actually get output to our console window on Windows. -#ifdef _WIN32 - -static bool s_debugger_attached = false; -static bool s_console_handle_set = false; -static bool s_console_allocated = false; -static HANDLE s_console_handle = INVALID_HANDLE_VALUE; -static HANDLE s_old_console_stdin = NULL; -static HANDLE s_old_console_stdout = NULL; -static HANDLE s_old_console_stderr = NULL; - -static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType) -{ - Console.WriteLn("Handler %u", dwCtrlType); - if (dwCtrlType != CTRL_C_EVENT) - return FALSE; - - ::raise(SIGTERM); - return TRUE; -} - -static bool EnableVirtualTerminalProcessing(HANDLE hConsole) -{ - if (hConsole == INVALID_HANDLE_VALUE) - return false; - - DWORD old_mode; - if (!GetConsoleMode(hConsole, &old_mode)) - return false; - - // already enabled? - if (old_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) - return true; - - return SetConsoleMode(hConsole, old_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); -} - -#endif - -static void ConsoleQt_SetTitle(const char* title) -{ -#ifdef _WIN32 - SetConsoleTitleW(StringUtil::UTF8StringToWideString(title).c_str()); -#else - std::fprintf(stdout, "\033]0;%s\007", title); -#endif -} - -static void ConsoleQt_DoSetColor(ConsoleColors color) -{ -#ifdef _WIN32 - if (s_console_handle == INVALID_HANDLE_VALUE) - return; - - const wchar_t* colortext = s_console_colors[static_cast(color)]; - DWORD written; - WriteConsoleW(s_console_handle, colortext, std::wcslen(colortext), &written, nullptr); -#else - const char* colortext = s_console_colors[static_cast(color)]; - std::fputs(colortext, stdout); -#endif -} - -static void ConsoleQt_DoWrite(const char* fmt) -{ - std::unique_lock lock(s_log_mutex); - -#ifdef _WIN32 - if (s_console_handle != INVALID_HANDLE_VALUE || s_debugger_attached) - { - // TODO: Put this on the stack. - std::wstring wfmt(StringUtil::UTF8StringToWideString(fmt)); - - if (s_debugger_attached) - OutputDebugStringW(wfmt.c_str()); - - if (s_console_handle != INVALID_HANDLE_VALUE) - { - DWORD written; - WriteConsoleW(s_console_handle, wfmt.c_str(), static_cast(wfmt.length()), &written, nullptr); - } - } -#else - std::fputs(fmt, stdout); -#endif - - if (emuLog) - { - std::fputs(fmt, emuLog); - } -} - -static void ConsoleQt_DoWriteLn(const char* fmt) -{ - std::unique_lock lock(s_log_mutex); - - // find time since start of process, but save a syscall if we're not writing timestamps - float message_time = - s_log_timestamps ? - static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_log_start_timestamp)) : - 0.0f; - - // split newlines up - const char* start = fmt; - do - { - const char* end = std::strchr(start, '\n'); - - std::string_view line; - if (end) - { - line = std::string_view(start, end - start); - start = end + 1; - } - else - { - line = std::string_view(start); - start = nullptr; - } - -#ifdef _WIN32 - if (s_console_handle != INVALID_HANDLE_VALUE || s_debugger_attached) - { - // TODO: Put this on the stack. - std::wstring wfmt(StringUtil::UTF8StringToWideString(line)); - - if (s_debugger_attached) - { - // VS already timestamps logs (at least with the productivity power tools). - if (!wfmt.empty()) - OutputDebugStringW(wfmt.c_str()); - OutputDebugStringW(L"\n"); - } - - if (s_console_handle != INVALID_HANDLE_VALUE) - { - DWORD written; - if (s_log_timestamps) - { - wchar_t timestamp_text[128]; - const int timestamp_len = _swprintf(timestamp_text, L"[%10.4f] ", message_time); - WriteConsoleW(s_console_handle, timestamp_text, static_cast(timestamp_len), &written, nullptr); - } - - if (!wfmt.empty()) - WriteConsoleW(s_console_handle, wfmt.c_str(), static_cast(wfmt.length()), &written, nullptr); - - WriteConsoleW(s_console_handle, L"\n", 1, &written, nullptr); - } - } -#else - if (s_log_timestamps) - { - std::fprintf(stdout, "[%10.4f] %.*s\n", message_time, static_cast(line.length()), line.data()); - } - else - { - if (!line.empty()) - std::fwrite(line.data(), line.length(), 1, stdout); - std::fputc('\n', stdout); - } -#endif - - if (emuLog) - { - if (s_log_timestamps) - { - std::fprintf(emuLog, "[%10.4f] %.*s\n", message_time, static_cast(line.length()), line.data()); - } - else - { - std::fwrite(line.data(), line.length(), 1, emuLog); - std::fputc('\n', emuLog); - } - } - } while (start); -} - -static void ConsoleQt_Newline() -{ - ConsoleQt_DoWriteLn(""); -} - -static const IConsoleWriter ConsoleWriter_WinQt = { - ConsoleQt_DoWrite, - ConsoleQt_DoWriteLn, - ConsoleQt_DoSetColor, - - ConsoleQt_DoWrite, - ConsoleQt_Newline, - ConsoleQt_SetTitle, -}; - -static void UpdateLoggingSinks(bool system_console, bool file_log) -{ -#ifdef _WIN32 - const bool debugger_attached = IsDebuggerPresent(); - s_debugger_attached = debugger_attached; - if (system_console) - { - if (!s_console_handle_set) - { - s_old_console_stdin = GetStdHandle(STD_INPUT_HANDLE); - s_old_console_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - s_old_console_stderr = GetStdHandle(STD_ERROR_HANDLE); - - bool handle_valid = (GetConsoleWindow() != NULL); - if (!handle_valid) - { - s_console_allocated = AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole(); - handle_valid = (GetConsoleWindow() != NULL); - } - - if (handle_valid) - { - s_console_handle = GetStdHandle(STD_OUTPUT_HANDLE); - if (s_console_handle != INVALID_HANDLE_VALUE) - { - s_console_handle_set = true; - SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); - - // This gets us unix-style coloured output. - EnableVirtualTerminalProcessing(GetStdHandle(STD_OUTPUT_HANDLE)); - EnableVirtualTerminalProcessing(GetStdHandle(STD_ERROR_HANDLE)); - - // Redirect stdout/stderr. - std::FILE* fp; - freopen_s(&fp, "CONIN$", "r", stdin); - freopen_s(&fp, "CONOUT$", "w", stdout); - freopen_s(&fp, "CONOUT$", "w", stderr); - } - } - } - - // just in case it fails - system_console = s_console_handle_set; - } - else - { - if (s_console_handle_set) - { - s_console_handle_set = false; - SetConsoleCtrlHandler(ConsoleCtrlHandler, FALSE); - - // redirect stdout/stderr back to null. - std::FILE* fp; - freopen_s(&fp, "NUL:", "w", stderr); - freopen_s(&fp, "NUL:", "w", stdout); - freopen_s(&fp, "NUL:", "w", stdin); - - // release console and restore state - SetStdHandle(STD_INPUT_HANDLE, s_old_console_stdin); - SetStdHandle(STD_OUTPUT_HANDLE, s_old_console_stdout); - SetStdHandle(STD_ERROR_HANDLE, s_old_console_stderr); - s_old_console_stdin = NULL; - s_old_console_stdout = NULL; - s_old_console_stderr = NULL; - if (s_console_allocated) - { - s_console_allocated = false; - FreeConsole(); - } - } - } -#else - const bool debugger_attached = false; -#endif - - if (file_log) - { - if (!emuLog) - { - if (emuLogName.empty()) - emuLogName = Path::Combine(EmuFolders::Logs, "emulog.txt"); - - emuLog = FileSystem::OpenCFile(emuLogName.c_str(), "wb"); - file_log = (emuLog != nullptr); - } - } - else - { - if (emuLog) - { - std::fclose(emuLog); - emuLog = nullptr; - emuLogName = {}; - } - } - - // Discard logs completely if there's no sinks. - if (debugger_attached || system_console || file_log) - Console_SetActiveHandler(ConsoleWriter_WinQt); - else - Console_SetActiveHandler(ConsoleWriter_Null); -} - -void LogSink::SetFileLogPath(std::string path) -{ - if (emuLogName == path) - return; - - emuLogName = std::move(path); - - // reopen on change - if (emuLog) - { - std::fclose(emuLog); - if (!emuLogName.empty()) - emuLog = FileSystem::OpenCFile(emuLogName.c_str(), "wb"); - } -} - -void LogSink::CloseFileLog() -{ - if (!emuLog) - return; - - std::fclose(emuLog); - emuLog = nullptr; -} - -void LogSink::SetBlockSystemConsole(bool block) -{ - s_block_system_console = block; -} - -void LogSink::InitializeEarlyConsole() -{ - UpdateLoggingSinks(true, false); -} - -void LogSink::UpdateLogging(SettingsInterface& si) -{ - const bool system_console_enabled = !s_block_system_console && si.GetBoolValue("Logging", "EnableSystemConsole", false); - const bool file_logging_enabled = si.GetBoolValue("Logging", "EnableFileLogging", false); - - s_log_timestamps = si.GetBoolValue("Logging", "EnableTimestamps", true); - - const bool any_logging_sinks = system_console_enabled || file_logging_enabled; - DevConWriterEnabled = any_logging_sinks && (IsDevBuild || si.GetBoolValue("Logging", "EnableVerbose", false)); - - const bool ee_console_enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableEEConsole", false); - SysConsole.eeConsole.Enabled = ee_console_enabled; - - SysConsole.iopConsole.Enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableIOPConsole", false); - SysTrace.IOP.R3000A.Enabled = true; - SysTrace.IOP.COP2.Enabled = true; - SysTrace.IOP.Memory.Enabled = true; - SysTrace.SIF.Enabled = true; - - // Input Recording Logs - SysConsole.recordingConsole.Enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableInputRecordingLogs", true); - SysConsole.controlInfo.Enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableControllerLogs", false); - - UpdateLoggingSinks(system_console_enabled, file_logging_enabled); -} - -void LogSink::SetDefaultLoggingSettings(SettingsInterface& si) -{ - si.SetBoolValue("Logging", "EnableSystemConsole", false); - si.SetBoolValue("Logging", "EnableFileLogging", false); - si.SetBoolValue("Logging", "EnableTimestamps", true); - si.SetBoolValue("Logging", "EnableVerbose", false); - si.SetBoolValue("Logging", "EnableEEConsole", false); - si.SetBoolValue("Logging", "EnableIOPConsole", false); - si.SetBoolValue("Logging", "EnableInputRecordingLogs", true); - si.SetBoolValue("Logging", "EnableControllerLogs", false); -} diff --git a/pcsx2/LogSink.h b/pcsx2/LogSink.h deleted file mode 100644 index 9e4dcb6998..0000000000 --- a/pcsx2/LogSink.h +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team -// SPDX-License-Identifier: LGPL-3.0+ - -#pragma once - -class SettingsInterface; - -namespace LogSink -{ - /// Overrides the filename used for the file log. - void SetFileLogPath(std::string path); - - /// Ensures file log is flushed and closed. - void CloseFileLog(); - - /// Prevents the system console from being displayed. - void SetBlockSystemConsole(bool block); - - /// Updates the Console handler based on the current configuration. - void UpdateLogging(SettingsInterface& si); - - /// Initializes early console logging (for printing command line arguments). - void InitializeEarlyConsole(); - - /// Stores default logging settings to the specified file. - void SetDefaultLoggingSettings(SettingsInterface& si); -} diff --git a/pcsx2/Memory.cpp b/pcsx2/Memory.cpp index 25fca5018e..c30cf0eb5e 100644 --- a/pcsx2/Memory.cpp +++ b/pcsx2/Memory.cpp @@ -180,7 +180,7 @@ bool SysMemory::AllocateMemoryMap() void SysMemory::DumpMemoryMap() { #define DUMP_REGION(name, base, offset, size) \ - DevCon.WriteLn(Color_Gray, "%-32s @ 0x%016" PRIXPTR " -> 0x%016" PRIXPTR " %s", name, \ + DevCon.WriteLn(Color_Gray, " %-32s @ 0x%016" PRIXPTR " -> 0x%016" PRIXPTR " %s", name, \ (uptr)(base + offset), (uptr)(base + offset + size), fmt::format("[{}mb]", size / _1mb).c_str()); DUMP_REGION("EE Main Memory", s_data_memory, HostMemoryMap::EEmemOffset, HostMemoryMap::EEmemSize); @@ -227,8 +227,6 @@ bool SysMemory::Allocate() { DevCon.WriteLn(Color_StrongBlue, "Allocating host memory for virtual systems..."); - ConsoleIndentScope indent(1); - if (!AllocateMemoryMap()) return false; @@ -245,7 +243,6 @@ bool SysMemory::Allocate() void SysMemory::Reset() { DevCon.WriteLn(Color_StrongBlue, "Resetting host memory for virtual systems..."); - ConsoleIndentScope indent(1); memReset(); iopMemReset(); @@ -258,7 +255,6 @@ void SysMemory::Reset() void SysMemory::Release() { Console.WriteLn(Color_Blue, "Releasing host memory for virtual systems..."); - ConsoleIndentScope indent(1); vtlb_Core_Free(); // Just to be sure... (calling order could result in it getting missed during Decommit). diff --git a/pcsx2/MultipartFileReader.cpp b/pcsx2/MultipartFileReader.cpp index 9d613d1c59..051aac3563 100644 --- a/pcsx2/MultipartFileReader.cpp +++ b/pcsx2/MultipartFileReader.cpp @@ -93,7 +93,6 @@ void MultipartFileReader::FindParts() return; DevCon.WriteLn( Color_Blue, "isoFile: multi-part %s detected...", StringUtil::toUpper(curext).c_str() ); - ConsoleIndentScope indent; int bsize = m_parts[0].reader->GetBlockSize(); int blocks = m_parts[0].end; diff --git a/pcsx2/Patch.cpp b/pcsx2/Patch.cpp index eaacc55e26..7d410a1fdd 100644 --- a/pcsx2/Patch.cpp +++ b/pcsx2/Patch.cpp @@ -7,6 +7,7 @@ #include "common/ByteSwap.h" #include "common/FileSystem.h" #include "common/Path.h" +#include "common/SmallString.h" #include "common/StringUtil.h" #include "common/ZipHelpers.h" @@ -99,9 +100,9 @@ namespace Patch bool operator==(const PatchCommand& p) const { return std::memcmp(this, &p, sizeof(*this)) == 0; } bool operator!=(const PatchCommand& p) const { return std::memcmp(this, &p, sizeof(*this)) != 0; } - std::string ToString() const + SmallString ToString() const { - return fmt::format("{},{},{},{:08x},{:x}", s_place_to_string[static_cast(placetopatch)], + return SmallString::from_fmt("{},{},{},{:08x},{:x}", s_place_to_string[static_cast(placetopatch)], s_cpu_to_string[static_cast(cpu)], s_type_to_string[static_cast(type)], addr, data); } }; @@ -616,8 +617,8 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable for (const PatchCommand& ip : p.patches) { // print the actual patch lines only in verbose mode (even in devel) - if (DevConWriterEnabled) - DevCon.Indent().WriteLn(ip.ToString()); + if (Log::GetMaxLevel() >= LOGLEVEL_DEV) + DevCon.WriteLnFmt(" {}", ip.ToString()); s_active_patches.push_back(&ip); } diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index 08d246d7f9..901b24d058 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -152,11 +152,10 @@ bool SaveStateBase::FreezeBios() if (bioscheck != BiosChecksum) { - Console.Newline(); - Console.Indent(1).Error( "Warning: BIOS Version Mismatch, savestate may be unstable!" ); - Console.Indent(2).Error( - "Current BIOS: %s (crc=0x%08x)\n" - "Savestate BIOS: %s (crc=0x%08x)\n", + Console.Error("\n Warning: BIOS Version Mismatch, savestate may be unstable!"); + Console.Error( + " Current BIOS: %s (crc=0x%08x)\n" + " Savestate BIOS: %s (crc=0x%08x)\n", BiosDescription.c_str(), BiosChecksum, biosdesc, bioscheck ); @@ -355,7 +354,7 @@ static bool SysState_ComponentFreezeIn(zip_file_t* zf, SysState_Component comp) if (comp.freeze(FreezeAction::Size, &fP) != 0) fP.size = 0; - Console.Indent().WriteLn("Loading %s", comp.name); + Console.WriteLn(" Loading %s", comp.name); std::unique_ptr data; if (fP.size > 0) @@ -394,7 +393,7 @@ static bool SysState_ComponentFreezeOut(SaveStateBase& writer, SysState_Componen const int size = fP.size; writer.PrepBlock(size); - Console.Indent().WriteLn("Saving %s", comp.name); + Console.WriteLn(" Saving %s", comp.name); fP.data = writer.GetBlockPtr(); if (comp.freeze(FreezeAction::Save, &fP) != 0) diff --git a/pcsx2/SourceLog.cpp b/pcsx2/SourceLog.cpp index 2e8acd64d6..65b977d2d1 100644 --- a/pcsx2/SourceLog.cpp +++ b/pcsx2/SourceLog.cpp @@ -25,7 +25,6 @@ using namespace R5900; FILE* emuLog; -std::string emuLogName; SysTraceLogPack SysTrace; SysConsoleLogPack SysConsole; diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 7b73c3c8c1..ab83c426a7 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -21,7 +21,6 @@ #include "ImGui/ImGuiOverlays.h" #include "Input/InputManager.h" #include "IopBios.h" -#include "LogSink.h" #include "MTGS.h" #include "MTVU.h" #include "PINE.h" @@ -79,6 +78,9 @@ namespace VMManager { + static void SetDefaultLoggingSettings(SettingsInterface& si); + static void UpdateLoggingSettings(SettingsInterface& si); + static void LogCPUCapabilities(); static void InitializeCPUProviders(); static void ShutdownCPUProviders(); @@ -145,6 +147,9 @@ static constexpr u32 SETTINGS_VERSION = 1; static std::unique_ptr s_game_settings_interface; static std::unique_ptr s_input_settings_interface; +static bool s_log_block_system_console = false; +static bool s_log_force_file_log = false; + static std::atomic s_state{VMState::Shutdown}; static bool s_cpu_implementation_changed = false; static Threading::ThreadHandle s_vm_thread_handle; @@ -411,6 +416,80 @@ void VMManager::Internal::CPUThreadShutdown() #ifdef _WIN32 CoUninitialize(); #endif + + // Ensure emulog gets flushed. + Log::SetFileOutputLevel(LOGLEVEL_NONE, std::string()); +} + +void VMManager::Internal::SetFileLogPath(std::string path) +{ + s_log_force_file_log = Log::SetFileOutputLevel(LOGLEVEL_DEBUG, std::move(path)); + emuLog = Log::GetFileLogHandle(); +} + +void VMManager::Internal::SetBlockSystemConsole(bool block) +{ + s_log_block_system_console = block; +} + +void VMManager::UpdateLoggingSettings(SettingsInterface& si) +{ +#ifdef _DEBUG + constexpr LOGLEVEL level = LOGLEVEL_DEBUG; +#else + const LOGLEVEL level = (IsDevBuild || si.GetBoolValue("Logging", "EnableVerbose", false)) ? LOGLEVEL_DEV : LOGLEVEL_INFO; +#endif + + const bool system_console_enabled = !s_log_block_system_console && si.GetBoolValue("Logging", "EnableSystemConsole", false); + const bool log_window_enabled = !s_log_block_system_console && si.GetBoolValue("Logging", "EnableLogWindow", false); + const bool file_logging_enabled = s_log_force_file_log || si.GetBoolValue("Logging", "EnableFileLogging", false); + + if (system_console_enabled != Log::IsConsoleOutputEnabled()) + Log::SetConsoleOutputLevel(system_console_enabled ? level : LOGLEVEL_NONE); + + if (file_logging_enabled != Log::IsFileOutputEnabled()) + { + std::string path = Path::Combine(EmuFolders::Logs, "emulog.txt"); + Log::SetFileOutputLevel(file_logging_enabled ? level : LOGLEVEL_NONE, std::move(path)); + } + + // Debug console only exists on Windows. +#ifdef _WIN32 + const bool debug_console_enabled = IsDebuggerPresent() && si.GetBoolValue("Logging", "EnableDebugConsole", false); + Log::SetDebugOutputLevel(debug_console_enabled ? level : LOGLEVEL_NONE); +#else + constexpr bool debug_console_enabled = false; +#endif + + const bool timestamps_enabled = si.GetBoolValue("Logging", "EnableTimestamps", true); + Log::SetTimestampsEnabled(timestamps_enabled); + + const bool any_logging_sinks = system_console_enabled || log_window_enabled || file_logging_enabled || debug_console_enabled; + + const bool ee_console_enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableEEConsole", false); + SysConsole.eeConsole.Enabled = ee_console_enabled; + + SysConsole.iopConsole.Enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableIOPConsole", false); + SysTrace.IOP.R3000A.Enabled = true; + SysTrace.IOP.COP2.Enabled = true; + SysTrace.IOP.Memory.Enabled = true; + SysTrace.SIF.Enabled = true; + + // Input Recording Logs + SysConsole.recordingConsole.Enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableInputRecordingLogs", true); + SysConsole.controlInfo.Enabled = any_logging_sinks && si.GetBoolValue("Logging", "EnableControllerLogs", false); +} + +void VMManager::SetDefaultLoggingSettings(SettingsInterface& si) +{ + si.SetBoolValue("Logging", "EnableSystemConsole", false); + si.SetBoolValue("Logging", "EnableFileLogging", false); + si.SetBoolValue("Logging", "EnableTimestamps", true); + si.SetBoolValue("Logging", "EnableVerbose", false); + si.SetBoolValue("Logging", "EnableEEConsole", false); + si.SetBoolValue("Logging", "EnableIOPConsole", false); + si.SetBoolValue("Logging", "EnableInputRecordingLogs", true); + si.SetBoolValue("Logging", "EnableControllerLogs", false); } bool VMManager::Internal::CheckSettingsVersion() @@ -426,7 +505,9 @@ void VMManager::Internal::LoadStartupSettings() SettingsInterface* bsi = Host::Internal::GetBaseSettingsLayer(); EmuFolders::LoadConfig(*bsi); EmuFolders::EnsureFoldersExist(); - LogSink::UpdateLogging(*bsi); + + // We need to create the console window early, otherwise it appears behind the main window. + UpdateLoggingSettings(*bsi); #ifdef ENABLE_RAINTEGRATION // RAIntegration switch must happen before the UI is created. @@ -455,7 +536,7 @@ void VMManager::SetDefaultSettings( si.SetBoolValue("EmuCore", "EnableFastBoot", true); SetHardwareDependentDefaultSettings(si); - LogSink::SetDefaultLoggingSettings(si); + SetDefaultLoggingSettings(si); } if (controllers) { @@ -482,7 +563,7 @@ void VMManager::LoadSettings() Host::LoadSettings(*si, lock); InputManager::ReloadSources(*si, lock); InputManager::ReloadBindings(*si, *Host::GetSettingsInterfaceForBindings()); - LogSink::UpdateLogging(*si); + UpdateLoggingSettings(*si); if (HasValidOrInitializingVM()) { @@ -2152,26 +2233,22 @@ void VMManager::LogCPUCapabilities() SVN_REV); } - Console.WriteLn("Savestate version: 0x%x", g_SaveVersion); - Console.Newline(); + Console.WriteLnFmt("Savestate version: 0x{:x}\n", g_SaveVersion); Console.WriteLn(Color_StrongBlack, "Host Machine Init:"); - Console.Indent().WriteLn( - "Operating System = %s\n" - "Physical RAM = %u MB", + Console.WriteLnFmt( + " Operating System = {}\n" + " Physical RAM = {} MB", + GetOSVersionString(), + GetPhysicalMemory() / _1mb); - GetOSVersionString().c_str(), - (u32)(GetPhysicalMemory() / _1mb)); - - Console.Indent().WriteLn("Processor = %s", cpuinfo_get_package(0)->name); - Console.Indent().WriteLn("Core Count = %u cores", cpuinfo_get_cores_count()); - Console.Indent().WriteLn("Thread Count = %u threads", cpuinfo_get_processors_count()); - - Console.Newline(); + Console.WriteLnFmt(" Processor = {}", cpuinfo_get_package(0)->name); + Console.WriteLnFmt(" Core Count = {} cores", cpuinfo_get_cores_count()); + Console.WriteLnFmt(" Thread Count = {} threads", cpuinfo_get_processors_count()); + Console.WriteLn(); std::string features; - if (cpuinfo_has_x86_avx()) features += "AVX "; if (cpuinfo_has_x86_avx2()) @@ -2180,9 +2257,8 @@ void VMManager::LogCPUCapabilities() StringUtil::StripWhitespace(&features); Console.WriteLn(Color_StrongBlack, "x86 Features Detected:"); - Console.Indent().WriteLn("%s", features.c_str()); - - Console.Newline(); + Console.WriteLnFmt(" {}", features); + Console.WriteLn(); } diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index c3c5ca2624..e10b5693e0 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -222,6 +222,12 @@ namespace VMManager /// Loads early settings. Call once on startup. void LoadStartupSettings(); + /// Overrides the filename used for the file log. + void SetFileLogPath(std::string path); + + /// Prevents the system console from being displayed. + void SetBlockSystemConsole(bool block); + /// Initializes common host state, called on the CPU thread. bool CPUThreadInitialize(); diff --git a/pcsx2/pcsx2.vcxproj b/pcsx2/pcsx2.vcxproj index 5e10c212e5..860439ee04 100644 --- a/pcsx2/pcsx2.vcxproj +++ b/pcsx2/pcsx2.vcxproj @@ -230,7 +230,6 @@ - @@ -578,7 +577,6 @@ - diff --git a/pcsx2/pcsx2.vcxproj.filters b/pcsx2/pcsx2.vcxproj.filters index fc14843f48..00b932b952 100644 --- a/pcsx2/pcsx2.vcxproj.filters +++ b/pcsx2/pcsx2.vcxproj.filters @@ -1331,9 +1331,6 @@ System - - Misc - Misc @@ -2253,9 +2250,6 @@ System - - Misc - Misc diff --git a/pcsx2/x86/ix86-32/iR5900.cpp b/pcsx2/x86/ix86-32/iR5900.cpp index ed213290e1..4127284ad6 100644 --- a/pcsx2/x86/ix86-32/iR5900.cpp +++ b/pcsx2/x86/ix86-32/iR5900.cpp @@ -2083,7 +2083,6 @@ static void memory_protect_recompiled_code(u32 startpc, u32 size) xJC(DispatchPageReset); // note: clearcnt is measured per-page, not per-block! - ConsoleColorScope cs(Color_Gray); eeRecPerfLog.Write("Manual block @ %08X : size =%3d page/offs = 0x%05X/0x%03X inpgsz = %d clearcnt = %d", startpc, size, inpage_ptr >> 12, inpage_ptr & 0xfff, inpage_sz, manual_counter[inpage_ptr >> 12]); } diff --git a/pcsx2/x86/newVif_UnpackSSE.cpp b/pcsx2/x86/newVif_UnpackSSE.cpp index 7a08df1799..325cb9421f 100644 --- a/pcsx2/x86/newVif_UnpackSSE.cpp +++ b/pcsx2/x86/newVif_UnpackSSE.cpp @@ -370,9 +370,9 @@ void VifUnpackSSE_Init() nVifGen(a, b, c); DevCon.WriteLn("Unpack function generation complete. Generated function statistics:"); - DevCon.Indent().WriteLn( - "Reserved buffer : %zu bytes @ 0x%016" PRIXPTR "\n" - "x86 code generated : %zu bytes\n", + DevCon.WriteLn( + " Reserved buffer : %zu bytes @ 0x%016" PRIXPTR "\n" + " x86 code generated : %zu bytes\n", SysMemory::GetVIFUnpackRecEnd() - SysMemory::GetVIFUnpackRec(), SysMemory::GetVIFUnpackRec(), xGetPtr() - SysMemory::GetVIFUnpackRec() diff --git a/updater/Updater.cpp b/updater/Updater.cpp index 8f02b35d2a..db772dbeae 100644 --- a/updater/Updater.cpp +++ b/updater/Updater.cpp @@ -31,36 +31,6 @@ static constexpr size_t kInputBufSize = ((size_t)1 << 18); static constexpr ISzAlloc g_Alloc = {SzAlloc, SzFree}; #endif -static std::FILE* s_file_console_stream; -static constexpr IConsoleWriter s_file_console_writer = { - [](const char* fmt) { // WriteRaw - std::fputs(fmt, s_file_console_stream); - std::fflush(s_file_console_stream); - }, - [](const char* fmt) { // DoWriteLn - std::fputs(fmt, s_file_console_stream); - std::fputc('\n', s_file_console_stream); - std::fflush(s_file_console_stream); - }, - [](ConsoleColors) { // DoSetColor - }, - [](const char* fmt) { // DoWriteFromStdout - std::fputs(fmt, s_file_console_stream); - std::fflush(s_file_console_stream); - }, - []() { // Newline - std::fputc('\n', s_file_console_stream); - std::fflush(s_file_console_stream); - }, - [](const char*) { // SetTitle - }}; - -static void CloseConsoleFile() -{ - if (s_file_console_stream) - std::fclose(s_file_console_stream); -} - Updater::Updater(ProgressCallback* progress) : m_progress(progress) { @@ -74,16 +44,11 @@ Updater::~Updater() void Updater::SetupLogging(ProgressCallback* progress, const std::string& destination_directory) { - const std::string log_path(Path::Combine(destination_directory, "updater.log")); - s_file_console_stream = FileSystem::OpenCFile(log_path.c_str(), "w"); - if (!s_file_console_stream) - { - progress->DisplayFormattedModalError("Failed to open log file '%s'", log_path.c_str()); - return; - } + Log::SetDebugOutputLevel(LOGLEVEL_DEBUG); - Console_SetActiveHandler(s_file_console_writer); - std::atexit(CloseConsoleFile); + std::string log_path = Path::Combine(destination_directory, "updater.log"); + if (!Log::SetFileOutputLevel(LOGLEVEL_DEBUG, std::move(log_path))) + progress->DisplayFormattedModalError("Failed to open log file '%s'", log_path.c_str()); } bool Updater::Initialize(std::string destination_directory)