diff options
Diffstat (limited to 'src/common')
26 files changed, 1078 insertions, 912 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 9d5a90762..15989708d 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -3,14 +3,15 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU set(SRCS break_points.cpp - console_listener.cpp emu_window.cpp extended_trace.cpp file_search.cpp file_util.cpp hash.cpp key_map.cpp - log_manager.cpp + logging/filter.cpp + logging/text_formatter.cpp + logging/backend.cpp math_util.cpp mem_arena.cpp memory_util.cpp @@ -32,7 +33,7 @@ set(HEADERS common_funcs.h common_paths.h common_types.h - console_listener.h + concurrent_ring_buffer.h cpu_detect.h debug_interface.h emu_window.h @@ -44,13 +45,17 @@ set(HEADERS key_map.h linear_disk_cache.h log.h - log_manager.h + logging/text_formatter.h + logging/filter.h + logging/log.h + logging/backend.h math_util.h mem_arena.h memory_util.h msg_handler.h platform.h scm_rev.h + scope_exit.h string_util.h swap.h symbols.h diff --git a/src/common/break_points.cpp b/src/common/break_points.cpp index 25528b864..587dbf40a 100644 --- a/src/common/break_points.cpp +++ b/src/common/break_points.cpp @@ -180,7 +180,7 @@ void TMemCheck::Action(DebugInterface *debug_interface, u32 iValue, u32 addr, { if (Log) { - INFO_LOG(MEMMAP, "CHK %08x (%s) %s%i %0*x at %08x (%s)", + LOG_DEBUG(Debug_Breakpoint, "CHK %08x (%s) %s%i %0*x at %08x (%s)", pc, debug_interface->getDescription(pc).c_str(), write ? "Write" : "Read", size*8, size*2, iValue, addr, debug_interface->getDescription(addr).c_str() diff --git a/src/common/chunk_file.h b/src/common/chunk_file.h index 32af74594..39a14dc81 100644 --- a/src/common/chunk_file.h +++ b/src/common/chunk_file.h @@ -154,7 +154,7 @@ public: Do(foundVersion); if (error == ERROR_FAILURE || foundVersion < minVer || foundVersion > ver) { - WARN_LOG(COMMON, "Savestate failure: wrong version %d found for %s", foundVersion, title); + LOG_ERROR(Common, "Savestate failure: wrong version %d found for %s", foundVersion, title); SetError(ERROR_FAILURE); return PointerWrapSection(*this, -1, title); } @@ -178,7 +178,14 @@ public: case MODE_READ: if (memcmp(data, *ptr, size) != 0) return false; break; case MODE_WRITE: memcpy(*ptr, data, size); break; case MODE_MEASURE: break; // MODE_MEASURE - don't need to do anything - case MODE_VERIFY: for(int i = 0; i < size; i++) _dbg_assert_msg_(COMMON, ((u8*)data)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break; + case MODE_VERIFY: + for (int i = 0; i < size; i++) { + _dbg_assert_msg_(Common, ((u8*)data)[i] == (*ptr)[i], + "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", + ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], + (*ptr)[i], (*ptr)[i], &(*ptr)[i]); + } + break; default: break; // throw an error? } (*ptr) += size; @@ -191,7 +198,14 @@ public: case MODE_READ: memcpy(data, *ptr, size); break; case MODE_WRITE: memcpy(*ptr, data, size); break; case MODE_MEASURE: break; // MODE_MEASURE - don't need to do anything - case MODE_VERIFY: for(int i = 0; i < size; i++) _dbg_assert_msg_(COMMON, ((u8*)data)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break; + case MODE_VERIFY: + for (int i = 0; i < size; i++) { + _dbg_assert_msg_(Common, ((u8*)data)[i] == (*ptr)[i], + "Savestate verification failure: %d (0x%X) (at %p) != %d (0x%X) (at %p).\n", + ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], + (*ptr)[i], (*ptr)[i], &(*ptr)[i]); + } + break; default: break; // throw an error? } (*ptr) += size; @@ -476,7 +490,7 @@ public: break; default: - ERROR_LOG(COMMON, "Savestate error: invalid mode %d.", mode); + LOG_ERROR(Common, "Savestate error: invalid mode %d.", mode); } } @@ -490,7 +504,12 @@ public: case MODE_READ: x = (char*)*ptr; break; case MODE_WRITE: memcpy(*ptr, x.c_str(), stringLen); break; case MODE_MEASURE: break; - case MODE_VERIFY: _dbg_assert_msg_(COMMON, !strcmp(x.c_str(), (char*)*ptr), "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n", x.c_str(), (char*)*ptr, ptr); break; + case MODE_VERIFY: + _dbg_assert_msg_(Common, + !strcmp(x.c_str(), (char*)*ptr), + "Savestate verification failure: \"%s\" != \"%s\" (at %p).\n", + x.c_str(), (char*)*ptr, ptr); + break; } (*ptr) += stringLen; } @@ -504,7 +523,11 @@ public: case MODE_READ: x = (wchar_t*)*ptr; break; case MODE_WRITE: memcpy(*ptr, x.c_str(), stringLen); break; case MODE_MEASURE: break; - case MODE_VERIFY: _dbg_assert_msg_(COMMON, x == (wchar_t*)*ptr, "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n", x.c_str(), (wchar_t*)*ptr, ptr); break; + case MODE_VERIFY: + _dbg_assert_msg_(Common, x == (wchar_t*)*ptr, + "Savestate verification failure: \"%ls\" != \"%ls\" (at %p).\n", + x.c_str(), (wchar_t*)*ptr, ptr); + break; } (*ptr) += stringLen; } diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index db041780a..67b3679b0 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -5,6 +5,7 @@ #pragma once #include "common_types.h" +#include <cstdlib> #ifdef _WIN32 #define SLEEP(x) Sleep(x) @@ -39,8 +40,6 @@ template<> struct CompileTimeAssert<true> {}; #include <sys/endian.h> #endif -#include "common_types.h" - // go to debugger mode #ifdef GEKKO #define Crash() diff --git a/src/common/common_types.h b/src/common/common_types.h index fcc8b9a4e..c74c74f0f 100644 --- a/src/common/common_types.h +++ b/src/common/common_types.h @@ -41,8 +41,6 @@ typedef std::int64_t s64; ///< 64-bit signed int typedef float f32; ///< 32-bit floating point typedef double f64; ///< 64-bit floating point -#include "common/common.h" - /// Union for fast 16-bit type casting union t16 { u8 _u8[2]; ///< 8-bit unsigned char(s) diff --git a/src/common/concurrent_ring_buffer.h b/src/common/concurrent_ring_buffer.h new file mode 100644 index 000000000..2951d93db --- /dev/null +++ b/src/common/concurrent_ring_buffer.h @@ -0,0 +1,164 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <condition_variable> +#include <cstdint> +#include <mutex> +#include <thread> + +#include "common/common.h" // for NonCopyable +#include "common/log.h" // for _dbg_assert_ + +namespace Common { + +/** + * A MPMC (Multiple-Producer Multiple-Consumer) concurrent ring buffer. This data structure permits + * multiple threads to push and pop from a queue of bounded size. + */ +template <typename T, size_t ArraySize> +class ConcurrentRingBuffer : private NonCopyable { +public: + /// Value returned by the popping functions when the queue has been closed. + static const size_t QUEUE_CLOSED = -1; + + ConcurrentRingBuffer() {} + + ~ConcurrentRingBuffer() { + // If for whatever reason the queue wasn't completely drained, destroy the left over items. + for (size_t i = reader_index, end = writer_index; i != end; i = (i + 1) % ArraySize) { + Data()[i].~T(); + } + } + + /** + * Pushes a value to the queue. If the queue is full, this method will block. Does nothing if + * the queue is closed. + */ + void Push(T val) { + std::unique_lock<std::mutex> lock(mutex); + if (closed) { + return; + } + + // If the buffer is full, wait + writer.wait(lock, [&]{ + return (writer_index + 1) % ArraySize != reader_index; + }); + + T* item = &Data()[writer_index]; + new (item) T(std::move(val)); + + writer_index = (writer_index + 1) % ArraySize; + + // Wake up waiting readers + lock.unlock(); + reader.notify_one(); + } + + /** + * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will not + * block, and might return 0 values if there are no elements in the queue when it is called. + * + * @return The number of elements stored in `dest`. If the queue has been closed, returns + * `QUEUE_CLOSED`. + */ + size_t Pop(T* dest, size_t dest_len) { + std::unique_lock<std::mutex> lock(mutex); + if (closed && !CanRead()) { + return QUEUE_CLOSED; + } + return PopInternal(dest, dest_len); + } + + /** + * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will block + * if there are no elements in the queue when it is called. + * + * @return The number of elements stored in `dest`. If the queue has been closed, returns + * `QUEUE_CLOSED`. + */ + size_t BlockingPop(T* dest, size_t dest_len) { + std::unique_lock<std::mutex> lock(mutex); + if (closed && !CanRead()) { + return QUEUE_CLOSED; + } + + while (!CanRead()) { + reader.wait(lock); + if (closed && !CanRead()) { + return QUEUE_CLOSED; + } + } + _dbg_assert_(Common, CanRead()); + return PopInternal(dest, dest_len); + } + + /** + * Closes the queue. After calling this method, `Push` operations won't have any effect, and + * `PopMany` and `PopManyBlock` will start returning `QUEUE_CLOSED`. This is intended to allow + * a graceful shutdown of all consumers. + */ + void Close() { + std::unique_lock<std::mutex> lock(mutex); + closed = true; + // We need to wake up any reader that are waiting for an item that will never come. + lock.unlock(); + reader.notify_all(); + } + + /// Returns true if `Close()` has been called. + bool IsClosed() const { + return closed; + } + +private: + size_t PopInternal(T* dest, size_t dest_len) { + size_t output_count = 0; + while (output_count < dest_len && CanRead()) { + _dbg_assert_(Common, CanRead()); + + T* item = &Data()[reader_index]; + T out_val = std::move(*item); + item->~T(); + + size_t prev_index = (reader_index + ArraySize - 1) % ArraySize; + reader_index = (reader_index + 1) % ArraySize; + if (writer_index == prev_index) { + writer.notify_one(); + } + dest[output_count++] = std::move(out_val); + } + return output_count; + } + + bool CanRead() const { + return reader_index != writer_index; + } + + T* Data() { + return static_cast<T*>(static_cast<void*>(&storage)); + } + + /// Storage for entries + typename std::aligned_storage<ArraySize * sizeof(T), + std::alignment_of<T>::value>::type storage; + + /// Data is valid in the half-open interval [reader, writer). If they are `QUEUE_CLOSED` then the + /// queue has been closed. + size_t writer_index = 0, reader_index = 0; + // True if the queue has been closed. + bool closed = false; + + /// Mutex that protects the entire data structure. + std::mutex mutex; + /// Signaling wakes up reader which is waiting for storage to be non-empty. + std::condition_variable reader; + /// Signaling wakes up writer which is waiting for storage to be non-full. + std::condition_variable writer; +}; + +} // namespace diff --git a/src/common/console_listener.cpp b/src/common/console_listener.cpp deleted file mode 100644 index b6042796d..000000000 --- a/src/common/console_listener.cpp +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#include <algorithm> - -#ifdef _WIN32 -#include <windows.h> -#include <array> -#endif - -#include "common/common.h" -#include "common/log_manager.h" // Common -#include "common/console_listener.h" // Common - -ConsoleListener::ConsoleListener() -{ -#ifdef _WIN32 - hConsole = nullptr; - bUseColor = true; -#else - bUseColor = isatty(fileno(stdout)); -#endif -} - -ConsoleListener::~ConsoleListener() -{ - Close(); -} - -// 100, 100, "Dolphin Log Console" -// Open console window - width and height is the size of console window -// Name is the window title -void ConsoleListener::Open(bool Hidden, int Width, int Height, const char *Title) -{ -#ifdef _WIN32 - if (!GetConsoleWindow()) - { - // Open the console window and create the window handle for GetStdHandle() - AllocConsole(); - // Hide - if (Hidden) ShowWindow(GetConsoleWindow(), SW_HIDE); - // Save the window handle that AllocConsole() created - hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - // Set the console window title - SetConsoleTitle(Common::UTF8ToTStr(Title).c_str()); - // Set letter space - LetterSpace(80, 4000); - //MoveWindow(GetConsoleWindow(), 200,200, 800,800, true); - } - else - { - hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - } -#endif -} - -void ConsoleListener::UpdateHandle() -{ -#ifdef _WIN32 - hConsole = GetStdHandle(STD_OUTPUT_HANDLE); -#endif -} - -// Close the console window and close the eventual file handle -void ConsoleListener::Close() -{ -#ifdef _WIN32 - if (hConsole == nullptr) - return; - FreeConsole(); - hConsole = nullptr; -#else - fflush(nullptr); -#endif -} - -bool ConsoleListener::IsOpen() -{ -#ifdef _WIN32 - return (hConsole != nullptr); -#else - return true; -#endif -} - -/* - LetterSpace: SetConsoleScreenBufferSize and SetConsoleWindowInfo are - dependent on each other, that's the reason for the additional checks. -*/ -void ConsoleListener::BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst) -{ -#ifdef _WIN32 - BOOL SB, SW; - if (BufferFirst) - { - // Change screen buffer size - COORD Co = {BufferWidth, BufferHeight}; - SB = SetConsoleScreenBufferSize(hConsole, Co); - // Change the screen buffer window size - SMALL_RECT coo = {0,0,ScreenWidth, ScreenHeight}; // top, left, right, bottom - SW = SetConsoleWindowInfo(hConsole, TRUE, &coo); - } - else - { - // Change the screen buffer window size - SMALL_RECT coo = {0,0, ScreenWidth, ScreenHeight}; // top, left, right, bottom - SW = SetConsoleWindowInfo(hConsole, TRUE, &coo); - // Change screen buffer size - COORD Co = {BufferWidth, BufferHeight}; - SB = SetConsoleScreenBufferSize(hConsole, Co); - } -#endif -} -void ConsoleListener::LetterSpace(int Width, int Height) -{ -#ifdef _WIN32 - // Get console info - CONSOLE_SCREEN_BUFFER_INFO ConInfo; - GetConsoleScreenBufferInfo(hConsole, &ConInfo); - - // - int OldBufferWidth = ConInfo.dwSize.X; - int OldBufferHeight = ConInfo.dwSize.Y; - int OldScreenWidth = (ConInfo.srWindow.Right - ConInfo.srWindow.Left); - int OldScreenHeight = (ConInfo.srWindow.Bottom - ConInfo.srWindow.Top); - // - int NewBufferWidth = Width; - int NewBufferHeight = Height; - int NewScreenWidth = NewBufferWidth - 1; - int NewScreenHeight = OldScreenHeight; - - // Width - BufferWidthHeight(NewBufferWidth, OldBufferHeight, NewScreenWidth, OldScreenHeight, (NewBufferWidth > OldScreenWidth-1)); - // Height - BufferWidthHeight(NewBufferWidth, NewBufferHeight, NewScreenWidth, NewScreenHeight, (NewBufferHeight > OldScreenHeight-1)); - - // Resize the window too - //MoveWindow(GetConsoleWindow(), 200,200, (Width*8 + 50),(NewScreenHeight*12 + 200), true); -#endif -} -#ifdef _WIN32 -COORD ConsoleListener::GetCoordinates(int BytesRead, int BufferWidth) -{ - COORD Ret = {0, 0}; - // Full rows - int Step = (int)floor((float)BytesRead / (float)BufferWidth); - Ret.Y += Step; - // Partial row - Ret.X = BytesRead - (BufferWidth * Step); - return Ret; -} -#endif -void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool Resize) -{ -#ifdef _WIN32 - // Check size - if (Width < 8 || Height < 12) return; - - bool DBef = true; - bool DAft = true; - std::string SLog = ""; - - const HWND hWnd = GetConsoleWindow(); - const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - - // Get console info - CONSOLE_SCREEN_BUFFER_INFO ConInfo; - GetConsoleScreenBufferInfo(hConsole, &ConInfo); - DWORD BufferSize = ConInfo.dwSize.X * ConInfo.dwSize.Y; - - // --------------------------------------------------------------------- - // Save the current text - // ------------------------ - DWORD cCharsRead = 0; - COORD coordScreen = { 0, 0 }; - - static const int MAX_BYTES = 1024 * 16; - - std::vector<std::array<TCHAR, MAX_BYTES>> Str; - std::vector<std::array<WORD, MAX_BYTES>> Attr; - - // ReadConsoleOutputAttribute seems to have a limit at this level - static const int ReadBufferSize = MAX_BYTES - 32; - - DWORD cAttrRead = ReadBufferSize; - DWORD BytesRead = 0; - while (BytesRead < BufferSize) - { - Str.resize(Str.size() + 1); - if (!ReadConsoleOutputCharacter(hConsole, Str.back().data(), ReadBufferSize, coordScreen, &cCharsRead)) - SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error"); - - Attr.resize(Attr.size() + 1); - if (!ReadConsoleOutputAttribute(hConsole, Attr.back().data(), ReadBufferSize, coordScreen, &cAttrRead)) - SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error"); - - // Break on error - if (cAttrRead == 0) break; - BytesRead += cAttrRead; - coordScreen = GetCoordinates(BytesRead, ConInfo.dwSize.X); - } - // Letter space - int LWidth = (int)(floor((float)Width / 8.0f) - 1.0f); - int LHeight = (int)(floor((float)Height / 12.0f) - 1.0f); - int LBufWidth = LWidth + 1; - int LBufHeight = (int)floor((float)BufferSize / (float)LBufWidth); - // Change screen buffer size - LetterSpace(LBufWidth, LBufHeight); - - - ClearScreen(true); - coordScreen.Y = 0; - coordScreen.X = 0; - DWORD cCharsWritten = 0; - - int BytesWritten = 0; - DWORD cAttrWritten = 0; - for (size_t i = 0; i < Attr.size(); i++) - { - if (!WriteConsoleOutputCharacter(hConsole, Str[i].data(), ReadBufferSize, coordScreen, &cCharsWritten)) - SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error"); - if (!WriteConsoleOutputAttribute(hConsole, Attr[i].data(), ReadBufferSize, coordScreen, &cAttrWritten)) - SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error"); - - BytesWritten += cAttrWritten; - coordScreen = GetCoordinates(BytesWritten, LBufWidth); - } - - const int OldCursor = ConInfo.dwCursorPosition.Y * ConInfo.dwSize.X + ConInfo.dwCursorPosition.X; - COORD Coo = GetCoordinates(OldCursor, LBufWidth); - SetConsoleCursorPosition(hConsole, Coo); - - if (SLog.length() > 0) Log(LogTypes::LNOTICE, SLog.c_str()); - - // Resize the window too - if (Resize) MoveWindow(GetConsoleWindow(), Left,Top, (Width + 100),Height, true); -#endif -} - -void ConsoleListener::Log(LogTypes::LOG_LEVELS Level, const char *Text) -{ -#if defined(_WIN32) - WORD Color; - - switch (Level) - { - case OS_LEVEL: // light yellow - Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; - break; - case NOTICE_LEVEL: // light green - Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY; - break; - case ERROR_LEVEL: // light red - Color = FOREGROUND_RED | FOREGROUND_INTENSITY; - break; - case WARNING_LEVEL: // light purple - Color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; - break; - case INFO_LEVEL: // cyan - Color = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; - break; - case DEBUG_LEVEL: // gray - Color = FOREGROUND_INTENSITY; - break; - default: // off-white - Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - break; - } - SetConsoleTextAttribute(hConsole, Color); - printf(Text); -#else - char ColorAttr[16] = ""; - char ResetAttr[16] = ""; - - if (bUseColor) - { - strcpy(ResetAttr, "\033[0m"); - switch (Level) - { - case NOTICE_LEVEL: // light green - strcpy(ColorAttr, "\033[92m"); - break; - case ERROR_LEVEL: // light red - strcpy(ColorAttr, "\033[91m"); - break; - case WARNING_LEVEL: // light yellow - strcpy(ColorAttr, "\033[93m"); - break; - default: - break; - } - } - fprintf(stderr, "%s%s%s", ColorAttr, Text, ResetAttr); -#endif -} -// Clear console screen -void ConsoleListener::ClearScreen(bool Cursor) -{ -#if defined(_WIN32) - COORD coordScreen = { 0, 0 }; - DWORD cCharsWritten; - CONSOLE_SCREEN_BUFFER_INFO csbi; - DWORD dwConSize; - - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - - GetConsoleScreenBufferInfo(hConsole, &csbi); - dwConSize = csbi.dwSize.X * csbi.dwSize.Y; - // Write space to the entire console - FillConsoleOutputCharacter(hConsole, TEXT(' '), dwConSize, coordScreen, &cCharsWritten); - GetConsoleScreenBufferInfo(hConsole, &csbi); - FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten); - // Reset cursor - if (Cursor) SetConsoleCursorPosition(hConsole, coordScreen); -#endif -} - - diff --git a/src/common/console_listener.h b/src/common/console_listener.h deleted file mode 100644 index ebd90a105..000000000 --- a/src/common/console_listener.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#pragma once - -#include "common/log_manager.h" - -#ifdef _WIN32 -#include <windows.h> -#endif - -class ConsoleListener : public LogListener -{ -public: - ConsoleListener(); - ~ConsoleListener(); - - void Open(bool Hidden = false, int Width = 100, int Height = 100, const char * Name = "Console"); - void UpdateHandle(); - void Close(); - bool IsOpen(); - void LetterSpace(int Width, int Height); - void BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst); - void PixelSpace(int Left, int Top, int Width, int Height, bool); -#ifdef _WIN32 - COORD GetCoordinates(int BytesRead, int BufferWidth); -#endif - void Log(LogTypes::LOG_LEVELS, const char *Text) override; - void ClearScreen(bool Cursor = true); - -private: -#ifdef _WIN32 - HWND GetHwnd(void); - HANDLE hConsole; -#endif - bool bUseColor; -}; diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 7579d8c0f..88c46c117 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -88,7 +88,7 @@ bool IsDirectory(const std::string &filename) #endif if (result < 0) { - WARN_LOG(COMMON, "IsDirectory: stat failed on %s: %s", + LOG_WARNING(Common_Filesystem, "stat failed on %s: %s", filename.c_str(), GetLastErrorMsg()); return false; } @@ -100,33 +100,33 @@ bool IsDirectory(const std::string &filename) // Doesn't supports deleting a directory bool Delete(const std::string &filename) { - INFO_LOG(COMMON, "Delete: file %s", filename.c_str()); + LOG_INFO(Common_Filesystem, "file %s", filename.c_str()); // Return true because we care about the file no // being there, not the actual delete. if (!Exists(filename)) { - WARN_LOG(COMMON, "Delete: %s does not exist", filename.c_str()); + LOG_WARNING(Common_Filesystem, "%s does not exist", filename.c_str()); return true; } // We can't delete a directory if (IsDirectory(filename)) { - WARN_LOG(COMMON, "Delete failed: %s is a directory", filename.c_str()); + LOG_ERROR(Common_Filesystem, "Failed: %s is a directory", filename.c_str()); return false; } #ifdef _WIN32 if (!DeleteFile(Common::UTF8ToTStr(filename).c_str())) { - WARN_LOG(COMMON, "Delete: DeleteFile failed on %s: %s", + LOG_ERROR(Common_Filesystem, "DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg()); return false; } #else if (unlink(filename.c_str()) == -1) { - WARN_LOG(COMMON, "Delete: unlink failed on %s: %s", + LOG_ERROR(Common_Filesystem, "unlink failed on %s: %s", filename.c_str(), GetLastErrorMsg()); return false; } @@ -138,17 +138,17 @@ bool Delete(const std::string &filename) // Returns true if successful, or path already exists. bool CreateDir(const std::string &path) { - INFO_LOG(COMMON, "CreateDir: directory %s", path.c_str()); + LOG_TRACE(Common_Filesystem, "directory %s", path.c_str()); #ifdef _WIN32 if (::CreateDirectory(Common::UTF8ToTStr(path).c_str(), nullptr)) return true; DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) { - WARN_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: already exists", path.c_str()); + LOG_WARNING(Common_Filesystem, "CreateDirectory failed on %s: already exists", path.c_str()); return true; } - ERROR_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: %i", path.c_str(), error); + LOG_ERROR(Common_Filesystem, "CreateDirectory failed on %s: %i", path.c_str(), error); return false; #else if (mkdir(path.c_str(), 0755) == 0) @@ -158,11 +158,11 @@ bool CreateDir(const std::string &path) if (err == EEXIST) { - WARN_LOG(COMMON, "CreateDir: mkdir failed on %s: already exists", path.c_str()); + LOG_WARNING(Common_Filesystem, "mkdir failed on %s: already exists", path.c_str()); return true; } - ERROR_LOG(COMMON, "CreateDir: mkdir failed on %s: %s", path.c_str(), strerror(err)); + LOG_ERROR(Common_Filesystem, "mkdir failed on %s: %s", path.c_str(), strerror(err)); return false; #endif } @@ -171,11 +171,11 @@ bool CreateDir(const std::string &path) bool CreateFullPath(const std::string &fullPath) { int panicCounter = 100; - INFO_LOG(COMMON, "CreateFullPath: path %s", fullPath.c_str()); + LOG_TRACE(Common_Filesystem, "path %s", fullPath.c_str()); if (FileUtil::Exists(fullPath)) { - INFO_LOG(COMMON, "CreateFullPath: path exists %s", fullPath.c_str()); + LOG_WARNING(Common_Filesystem, "path exists %s", fullPath.c_str()); return true; } @@ -192,7 +192,7 @@ bool CreateFullPath(const std::string &fullPath) // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") std::string const subPath(fullPath.substr(0, position + 1)); if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) { - ERROR_LOG(COMMON, "CreateFullPath: directory creation failed"); + LOG_ERROR(Common, "CreateFullPath: directory creation failed"); return false; } @@ -200,7 +200,7 @@ bool CreateFullPath(const std::string &fullPath) panicCounter--; if (panicCounter <= 0) { - ERROR_LOG(COMMON, "CreateFullPath: directory structure is too deep"); + LOG_ERROR(Common, "CreateFullPath: directory structure is too deep"); return false; } position++; @@ -211,12 +211,12 @@ bool CreateFullPath(const std::string &fullPath) // Deletes a directory filename, returns true on success bool DeleteDir(const std::string &filename) { - INFO_LOG(COMMON, "DeleteDir: directory %s", filename.c_str()); + LOG_INFO(Common_Filesystem, "directory %s", filename.c_str()); // check if a directory if (!FileUtil::IsDirectory(filename)) { - ERROR_LOG(COMMON, "DeleteDir: Not a directory %s", filename.c_str()); + LOG_ERROR(Common_Filesystem, "Not a directory %s", filename.c_str()); return false; } @@ -227,7 +227,7 @@ bool DeleteDir(const std::string &filename) if (rmdir(filename.c_str()) == 0) return true; #endif - ERROR_LOG(COMMON, "DeleteDir: %s: %s", filename.c_str(), GetLastErrorMsg()); + LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg()); return false; } @@ -235,11 +235,11 @@ bool DeleteDir(const std::string &filename) // renames file srcFilename to destFilename, returns true on success bool Rename(const std::string &srcFilename, const std::string &destFilename) { - INFO_LOG(COMMON, "Rename: %s --> %s", + LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str()); if (rename(srcFilename.c_str(), destFilename.c_str()) == 0) return true; - ERROR_LOG(COMMON, "Rename: failed %s --> %s: %s", + LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); return false; } @@ -247,13 +247,13 @@ bool Rename(const std::string &srcFilename, const std::string &destFilename) // copies file srcFilename to destFilename, returns true on success bool Copy(const std::string &srcFilename, const std::string &destFilename) { - INFO_LOG(COMMON, "Copy: %s --> %s", + LOG_TRACE(Common_Filesystem, "%s --> %s", srcFilename.c_str(), destFilename.c_str()); #ifdef _WIN32 if (CopyFile(Common::UTF8ToTStr(srcFilename).c_str(), Common::UTF8ToTStr(destFilename).c_str(), FALSE)) return true; - ERROR_LOG(COMMON, "Copy: failed %s --> %s: %s", + LOG_ERROR(Common_Filesystem, "failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); return false; #else @@ -267,7 +267,7 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename) FILE *input = fopen(srcFilename.c_str(), "rb"); if (!input) { - ERROR_LOG(COMMON, "Copy: input failed %s --> %s: %s", + LOG_ERROR(Common_Filesystem, "opening input failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); return false; } @@ -277,7 +277,7 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename) if (!output) { fclose(input); - ERROR_LOG(COMMON, "Copy: output failed %s --> %s: %s", + LOG_ERROR(Common_Filesystem, "opening output failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); return false; } @@ -291,8 +291,8 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename) { if (ferror(input) != 0) { - ERROR_LOG(COMMON, - "Copy: failed reading from source, %s --> %s: %s", + LOG_ERROR(Common_Filesystem, + "failed reading from source, %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); goto bail; } @@ -302,8 +302,8 @@ bool Copy(const std::string &srcFilename, const std::string &destFilename) int wnum = fwrite(buffer, sizeof(char), rnum, output); if (wnum != rnum) { - ERROR_LOG(COMMON, - "Copy: failed writing to output, %s --> %s: %s", + LOG_ERROR(Common_Filesystem, + "failed writing to output, %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg()); goto bail; } @@ -326,13 +326,13 @@ u64 GetSize(const std::string &filename) { if (!Exists(filename)) { - WARN_LOG(COMMON, "GetSize: failed %s: No such file", filename.c_str()); + LOG_ERROR(Common_Filesystem, "failed %s: No such file", filename.c_str()); return 0; } if (IsDirectory(filename)) { - WARN_LOG(COMMON, "GetSize: failed %s: is a directory", filename.c_str()); + LOG_ERROR(Common_Filesystem, "failed %s: is a directory", filename.c_str()); return 0; } @@ -343,12 +343,12 @@ u64 GetSize(const std::string &filename) if (stat64(filename.c_str(), &buf) == 0) #endif { - DEBUG_LOG(COMMON, "GetSize: %s: %lld", + LOG_TRACE(Common_Filesystem, "%s: %lld", filename.c_str(), (long long)buf.st_size); return buf.st_size; } - ERROR_LOG(COMMON, "GetSize: Stat failed %s: %s", + LOG_ERROR(Common_Filesystem, "Stat failed %s: %s", filename.c_str(), GetLastErrorMsg()); return 0; } @@ -358,7 +358,7 @@ u64 GetSize(const int fd) { struct stat64 buf; if (fstat64(fd, &buf) != 0) { - ERROR_LOG(COMMON, "GetSize: stat failed %i: %s", + LOG_ERROR(Common_Filesystem, "GetSize: stat failed %i: %s", fd, GetLastErrorMsg()); return 0; } @@ -371,13 +371,13 @@ u64 GetSize(FILE *f) // can't use off_t here because it can be 32-bit u64 pos = ftello(f); if (fseeko(f, 0, SEEK_END) != 0) { - ERROR_LOG(COMMON, "GetSize: seek failed %p: %s", + LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg()); return 0; } u64 size = ftello(f); if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) { - ERROR_LOG(COMMON, "GetSize: seek failed %p: %s", + LOG_ERROR(Common_Filesystem, "GetSize: seek failed %p: %s", f, GetLastErrorMsg()); return 0; } @@ -387,11 +387,11 @@ u64 GetSize(FILE *f) // creates an empty file filename, returns true on success bool CreateEmptyFile(const std::string &filename) { - INFO_LOG(COMMON, "CreateEmptyFile: %s", filename.c_str()); + LOG_TRACE(Common_Filesystem, "%s", filename.c_str()); if (!FileUtil::IOFile(filename, "wb")) { - ERROR_LOG(COMMON, "CreateEmptyFile: failed %s: %s", + LOG_ERROR(Common_Filesystem, "failed %s: %s", filename.c_str(), GetLastErrorMsg()); return false; } @@ -404,7 +404,7 @@ bool CreateEmptyFile(const std::string &filename) // results into parentEntry. Returns the number of files+directories found u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry) { - INFO_LOG(COMMON, "ScanDirectoryTree: directory %s", directory.c_str()); + LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str()); // How many files + directories we found u32 foundEntries = 0; #ifdef _WIN32 @@ -474,7 +474,7 @@ u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry) // Deletes the given directory and anything under it. Returns true on success. bool DeleteDirRecursively(const std::string &directory) { - INFO_LOG(COMMON, "DeleteDirRecursively: %s", directory.c_str()); + LOG_TRACE(Common_Filesystem, "%s", directory.c_str()); #ifdef _WIN32 // Find the first file in the directory. WIN32_FIND_DATA ffd; @@ -588,7 +588,7 @@ std::string GetCurrentDir() // Get the current working directory (getcwd uses malloc) if (!(dir = __getcwd(nullptr, 0))) { - ERROR_LOG(COMMON, "GetCurrentDirectory failed: %s", + LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: %s", GetLastErrorMsg()); return nullptr; } @@ -647,7 +647,7 @@ std::string GetSysDirectory() #endif sysDir += DIR_SEP; - INFO_LOG(COMMON, "GetSysDirectory: Setting to %s:", sysDir.c_str()); + LOG_DEBUG(Common_Filesystem, "Setting to %s:", sysDir.c_str()); return sysDir; } @@ -695,7 +695,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new { if (!FileUtil::IsDirectory(newPath)) { - WARN_LOG(COMMON, "Invalid path specified %s", newPath.c_str()); + LOG_ERROR(Common_Filesystem, "Invalid path specified %s", newPath.c_str()); return paths[DirIDX]; } else diff --git a/src/common/log.h b/src/common/log.h index 78f0dae4d..663eda9ad 100644 --- a/src/common/log.h +++ b/src/common/log.h @@ -6,105 +6,7 @@ #include "common/common_funcs.h" #include "common/msg_handler.h" - -#ifndef LOGGING -#define LOGGING -#endif - -enum { - OS_LEVEL, // Printed by the emulated operating system - NOTICE_LEVEL, // VERY important information that is NOT errors. Like startup and OSReports. - ERROR_LEVEL, // Critical errors - WARNING_LEVEL, // Something is suspicious. - INFO_LEVEL, // General information. - DEBUG_LEVEL, // Detailed debugging - might make things slow. -}; - -namespace LogTypes -{ - -enum LOG_TYPE { - ACTIONREPLAY, - AUDIO, - AUDIO_INTERFACE, - BOOT, - COMMANDPROCESSOR, - COMMON, - CONSOLE, - CONFIG, - DISCIO, - FILEMON, - DSPHLE, - DSPLLE, - DSP_MAIL, - DSPINTERFACE, - DVDINTERFACE, - DYNA_REC, - EXPANSIONINTERFACE, - GDB_STUB, - ARM11, - GSP, - OSHLE, - MASTER_LOG, - MEMMAP, - MEMCARD_MANAGER, - OSREPORT, - PAD, - PROCESSORINTERFACE, - PIXELENGINE, - SERIALINTERFACE, - SP1, - STREAMINGINTERFACE, - VIDEO, - VIDEOINTERFACE, - LOADER, - FILESYS, - WII_IPC_DVD, - WII_IPC_ES, - WII_IPC_FILEIO, - WII_IPC_HID, - KERNEL, - SVC, - HLE, - RENDER, - GPU, - HW, - TIME, - NETPLAY, - GUI, - - NUMBER_OF_LOGS // Must be last -}; - -// FIXME: should this be removed? -enum LOG_LEVELS { - LOS = OS_LEVEL, - LNOTICE = NOTICE_LEVEL, - LERROR = ERROR_LEVEL, - LWARNING = WARNING_LEVEL, - LINFO = INFO_LEVEL, - LDEBUG = DEBUG_LEVEL, -}; - -#define LOGTYPES_LEVELS LogTypes::LOG_LEVELS -#define LOGTYPES_TYPE LogTypes::LOG_TYPE - -} // namespace - -void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int line, - const char* function, const char* fmt, ...) -#ifdef __GNUC__ - __attribute__((format(printf, 6, 7))) -#endif - ; - -#if defined LOGGING || defined _DEBUG || defined DEBUGFAST -#define MAX_LOGLEVEL LDEBUG -#else -#ifndef MAX_LOGLEVEL -#define MAX_LOGLEVEL LWARNING -#endif // loglevel -#endif // logging +#include "common/logging/log.h" #ifdef _WIN32 #ifndef __func__ @@ -112,29 +14,16 @@ void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int #endif #endif -// Let the compiler optimize this out -#define GENERIC_LOG(t, v, ...) { \ - if (v <= LogTypes::MAX_LOGLEVEL) \ - GenericLog(v, t, __FILE__, __LINE__, __func__, __VA_ARGS__); \ - } - -#define OS_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LOS, __VA_ARGS__) } while (0) -#define ERROR_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LERROR, __VA_ARGS__) } while (0) -#define WARN_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LWARNING, __VA_ARGS__) } while (0) -#define NOTICE_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LNOTICE, __VA_ARGS__) } while (0) -#define INFO_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LINFO, __VA_ARGS__) } while (0) -#define DEBUG_LOG(t,...) do { GENERIC_LOG(LogTypes::t, LogTypes::LDEBUG, __VA_ARGS__) } while (0) - -#if MAX_LOGLEVEL >= DEBUG_LEVEL +#if _DEBUG #define _dbg_assert_(_t_, _a_) \ if (!(_a_)) {\ - ERROR_LOG(_t_, "Error...\n\n Line: %d\n File: %s\n Time: %s\n\nIgnore and continue?", \ + LOG_CRITICAL(_t_, "Error...\n\n Line: %d\n File: %s\n Time: %s\n\nIgnore and continue?", \ __LINE__, __FILE__, __TIME__); \ if (!PanicYesNo("*** Assertion (see log)***\n")) {Crash();} \ } #define _dbg_assert_msg_(_t_, _a_, ...)\ if (!(_a_)) {\ - ERROR_LOG(_t_, __VA_ARGS__); \ + LOG_CRITICAL(_t_, __VA_ARGS__); \ if (!PanicYesNo(__VA_ARGS__)) {Crash();} \ } #define _dbg_update_() Host_UpdateLogDisplay(); @@ -146,11 +35,10 @@ void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int #define _dbg_assert_(_t_, _a_) {} #define _dbg_assert_msg_(_t_, _a_, _desc_, ...) {} #endif // dbg_assert -#endif // MAX_LOGLEVEL DEBUG +#endif #define _assert_(_a_) _dbg_assert_(MASTER_LOG, _a_) -#ifndef GEKKO #ifdef _WIN32 #define _assert_msg_(_t_, _a_, _fmt_, ...) \ if (!(_a_)) {\ @@ -162,6 +50,3 @@ void GenericLog(LOGTYPES_LEVELS level, LOGTYPES_TYPE type, const char*file, int if (!PanicYesNo(_fmt_, ##__VA_ARGS__)) {Crash();} \ } #endif // WIN32 -#else // GEKKO -#define _assert_msg_(_t_, _a_, _fmt_, ...) -#endif diff --git a/src/common/log_manager.cpp b/src/common/log_manager.cpp deleted file mode 100644 index 128c15388..000000000 --- a/src/common/log_manager.cpp +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#include <algorithm> - -#include "common/log_manager.h" -#include "common/console_listener.h" -#include "common/timer.h" - -void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line, - const char* function, const char* fmt, ...) -{ - va_list args; - va_start(args, fmt); - - if (LogManager::GetInstance()) { - LogManager::GetInstance()->Log(level, type, - file, line, function, fmt, args); - } - va_end(args); -} - -LogManager *LogManager::m_logManager = nullptr; - -LogManager::LogManager() -{ - // create log files - m_Log[LogTypes::MASTER_LOG] = new LogContainer("*", "Master Log"); - m_Log[LogTypes::BOOT] = new LogContainer("BOOT", "Boot"); - m_Log[LogTypes::COMMON] = new LogContainer("COMMON", "Common"); - m_Log[LogTypes::CONFIG] = new LogContainer("CONFIG", "Configuration"); - m_Log[LogTypes::DISCIO] = new LogContainer("DIO", "Disc IO"); - m_Log[LogTypes::FILEMON] = new LogContainer("FileMon", "File Monitor"); - m_Log[LogTypes::PAD] = new LogContainer("PAD", "Pad"); - m_Log[LogTypes::PIXELENGINE] = new LogContainer("PE", "PixelEngine"); - m_Log[LogTypes::COMMANDPROCESSOR] = new LogContainer("CP", "CommandProc"); - m_Log[LogTypes::VIDEOINTERFACE] = new LogContainer("VI", "VideoInt"); - m_Log[LogTypes::SERIALINTERFACE] = new LogContainer("SI", "SerialInt"); - m_Log[LogTypes::PROCESSORINTERFACE] = new LogContainer("PI", "ProcessorInt"); - m_Log[LogTypes::MEMMAP] = new LogContainer("MI", "MI & memmap"); - m_Log[LogTypes::SP1] = new LogContainer("SP1", "Serial Port 1"); - m_Log[LogTypes::STREAMINGINTERFACE] = new LogContainer("Stream", "StreamingInt"); - m_Log[LogTypes::DSPINTERFACE] = new LogContainer("DSP", "DSPInterface"); - m_Log[LogTypes::DVDINTERFACE] = new LogContainer("DVD", "DVDInterface"); - m_Log[LogTypes::GSP] = new LogContainer("GSP", "GSP"); - m_Log[LogTypes::EXPANSIONINTERFACE] = new LogContainer("EXI", "ExpansionInt"); - m_Log[LogTypes::GDB_STUB] = new LogContainer("GDB_STUB", "GDB Stub"); - m_Log[LogTypes::AUDIO_INTERFACE] = new LogContainer("AI", "AudioInt"); - m_Log[LogTypes::ARM11] = new LogContainer("ARM11", "ARM11"); - m_Log[LogTypes::OSHLE] = new LogContainer("HLE", "HLE"); - m_Log[LogTypes::DSPHLE] = new LogContainer("DSPHLE", "DSP HLE"); - m_Log[LogTypes::DSPLLE] = new LogContainer("DSPLLE", "DSP LLE"); - m_Log[LogTypes::DSP_MAIL] = new LogContainer("DSPMails", "DSP Mails"); - m_Log[LogTypes::VIDEO] = new LogContainer("Video", "Video Backend"); - m_Log[LogTypes::AUDIO] = new LogContainer("Audio", "Audio Emulator"); - m_Log[LogTypes::DYNA_REC] = new LogContainer("JIT", "JIT"); - m_Log[LogTypes::CONSOLE] = new LogContainer("CONSOLE", "Dolphin Console"); - m_Log[LogTypes::OSREPORT] = new LogContainer("OSREPORT", "OSReport"); - m_Log[LogTypes::TIME] = new LogContainer("Time", "Core Timing"); - m_Log[LogTypes::LOADER] = new LogContainer("Loader", "Loader"); - m_Log[LogTypes::FILESYS] = new LogContainer("FileSys", "File System"); - m_Log[LogTypes::WII_IPC_HID] = new LogContainer("WII_IPC_HID", "WII IPC HID"); - m_Log[LogTypes::KERNEL] = new LogContainer("KERNEL", "KERNEL HLE"); - m_Log[LogTypes::WII_IPC_DVD] = new LogContainer("WII_IPC_DVD", "WII IPC DVD"); - m_Log[LogTypes::WII_IPC_ES] = new LogContainer("WII_IPC_ES", "WII IPC ES"); - m_Log[LogTypes::WII_IPC_FILEIO] = new LogContainer("WII_IPC_FILEIO", "WII IPC FILEIO"); - m_Log[LogTypes::RENDER] = new LogContainer("RENDER", "RENDER"); - m_Log[LogTypes::GPU] = new LogContainer("GPU", "GPU"); - m_Log[LogTypes::SVC] = new LogContainer("SVC", "Supervisor Call HLE"); - m_Log[LogTypes::HLE] = new LogContainer("HLE", "High Level Emulation"); - m_Log[LogTypes::HW] = new LogContainer("HW", "Hardware"); - m_Log[LogTypes::ACTIONREPLAY] = new LogContainer("ActionReplay", "ActionReplay"); - m_Log[LogTypes::MEMCARD_MANAGER] = new LogContainer("MemCard Manager", "MemCard Manager"); - m_Log[LogTypes::NETPLAY] = new LogContainer("NETPLAY", "Netplay"); - m_Log[LogTypes::GUI] = new LogContainer("GUI", "GUI"); - - m_fileLog = new FileLogListener(FileUtil::GetUserPath(F_MAINLOG_IDX).c_str()); - m_consoleLog = new ConsoleListener(); - m_debuggerLog = new DebuggerLogListener(); - - for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) - { - m_Log[i]->SetEnable(true); - m_Log[i]->AddListener(m_fileLog); - m_Log[i]->AddListener(m_consoleLog); -#ifdef _MSC_VER - if (IsDebuggerPresent()) - m_Log[i]->AddListener(m_debuggerLog); -#endif - } - - m_consoleLog->Open(); -} - -LogManager::~LogManager() -{ - for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) - { - m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_fileLog); - m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_consoleLog); - m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_debuggerLog); - } - - for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) - delete m_Log[i]; - - delete m_fileLog; - delete m_consoleLog; - delete m_debuggerLog; -} - -void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, - int line, const char* function, const char *fmt, va_list args) -{ - char temp[MAX_MSGLEN]; - char msg[MAX_MSGLEN * 2]; - LogContainer *log = m_Log[type]; - - if (!log->IsEnabled() || level > log->GetLevel() || ! log->HasListeners()) - return; - - Common::CharArrayFromFormatV(temp, MAX_MSGLEN, fmt, args); - - static const char level_to_char[7] = "ONEWID"; - sprintf(msg, "%s %s:%u %c[%s] %s: %s\n", Common::Timer::GetTimeFormatted().c_str(), file, line, - level_to_char[(int)level], log->GetShortName(), function, temp); - -#ifdef ANDROID - Host_SysMessage(msg); -#endif - log->Trigger(level, msg); -} - -void LogManager::Init() -{ - m_logManager = new LogManager(); -} - -void LogManager::Shutdown() -{ - delete m_logManager; - m_logManager = nullptr; -} - -LogContainer::LogContainer(const char* shortName, const char* fullName, bool enable) - : m_enable(enable) -{ - strncpy(m_fullName, fullName, 128); - strncpy(m_shortName, shortName, 32); - m_level = LogTypes::MAX_LOGLEVEL; -} - -// LogContainer -void LogContainer::AddListener(LogListener *listener) -{ - std::lock_guard<std::mutex> lk(m_listeners_lock); - m_listeners.insert(listener); -} - -void LogContainer::RemoveListener(LogListener *listener) -{ - std::lock_guard<std::mutex> lk(m_listeners_lock); - m_listeners.erase(listener); -} - -void LogContainer::Trigger(LogTypes::LOG_LEVELS level, const char *msg) -{ - std::lock_guard<std::mutex> lk(m_listeners_lock); - - std::set<LogListener*>::const_iterator i; - for (i = m_listeners.begin(); i != m_listeners.end(); ++i) - { - (*i)->Log(level, msg); - } -} - -FileLogListener::FileLogListener(const char *filename) -{ - OpenFStream(m_logfile, filename, std::ios::app); - SetEnable(true); -} - -void FileLogListener::Log(LogTypes::LOG_LEVELS, const char *msg) -{ - if (!IsEnabled() || !IsValid()) - return; - - std::lock_guard<std::mutex> lk(m_log_lock); - m_logfile << msg << std::flush; -} - -void DebuggerLogListener::Log(LogTypes::LOG_LEVELS, const char *msg) -{ -#if _MSC_VER - ::OutputDebugStringA(msg); -#endif -} diff --git a/src/common/log_manager.h b/src/common/log_manager.h deleted file mode 100644 index baefc4ba8..000000000 --- a/src/common/log_manager.h +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#pragma once - -#include "common/log.h" -#include "common/string_util.h" -#include "common/file_util.h" - -#include <cstring> -#include <set> -#include <mutex> - -#define MAX_MESSAGES 8000 -#define MAX_MSGLEN 1024 - - -// pure virtual interface -class LogListener -{ -public: - virtual ~LogListener() {} - - virtual void Log(LogTypes::LOG_LEVELS, const char *msg) = 0; -}; - -class FileLogListener : public LogListener -{ -public: - FileLogListener(const char *filename); - - void Log(LogTypes::LOG_LEVELS, const char *msg) override; - - bool IsValid() { return !m_logfile.fail(); } - bool IsEnabled() const { return m_enable; } - void SetEnable(bool enable) { m_enable = enable; } - - const char* GetName() const { return "file"; } - -private: - std::mutex m_log_lock; - std::ofstream m_logfile; - bool m_enable; -}; - -class DebuggerLogListener : public LogListener -{ -public: - void Log(LogTypes::LOG_LEVELS, const char *msg) override; -}; - -class LogContainer -{ -public: - LogContainer(const char* shortName, const char* fullName, bool enable = false); - - const char* GetShortName() const { return m_shortName; } - const char* GetFullName() const { return m_fullName; } - - void AddListener(LogListener* listener); - void RemoveListener(LogListener* listener); - - void Trigger(LogTypes::LOG_LEVELS, const char *msg); - - bool IsEnabled() const { return m_enable; } - void SetEnable(bool enable) { m_enable = enable; } - - LogTypes::LOG_LEVELS GetLevel() const { return m_level; } - - void SetLevel(LogTypes::LOG_LEVELS level) { m_level = level; } - - bool HasListeners() const { return !m_listeners.empty(); } - -private: - char m_fullName[128]; - char m_shortName[32]; - bool m_enable; - LogTypes::LOG_LEVELS m_level; - std::mutex m_listeners_lock; - std::set<LogListener*> m_listeners; -}; - -class ConsoleListener; - -class LogManager : NonCopyable -{ -private: - LogContainer* m_Log[LogTypes::NUMBER_OF_LOGS]; - FileLogListener *m_fileLog; - ConsoleListener *m_consoleLog; - DebuggerLogListener *m_debuggerLog; - static LogManager *m_logManager; // Singleton. Ugh. - - LogManager(); - ~LogManager(); -public: - - static u32 GetMaxLevel() { return LogTypes::MAX_LOGLEVEL; } - - void Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line, - const char* function, const char *fmt, va_list args); - - void SetLogLevel(LogTypes::LOG_TYPE type, LogTypes::LOG_LEVELS level) - { - m_Log[type]->SetLevel(level); - } - - void SetEnable(LogTypes::LOG_TYPE type, bool enable) - { - m_Log[type]->SetEnable(enable); - } - - bool IsEnabled(LogTypes::LOG_TYPE type) const - { - return m_Log[type]->IsEnabled(); - } - - const char* GetShortName(LogTypes::LOG_TYPE type) const - { - return m_Log[type]->GetShortName(); - } - - const char* GetFullName(LogTypes::LOG_TYPE type) const - { - return m_Log[type]->GetFullName(); - } - - void AddListener(LogTypes::LOG_TYPE type, LogListener *listener) - { - m_Log[type]->AddListener(listener); - } - - void RemoveListener(LogTypes::LOG_TYPE type, LogListener *listener) - { - m_Log[type]->RemoveListener(listener); - } - - FileLogListener *GetFileListener() const - { - return m_fileLog; - } - - ConsoleListener *GetConsoleListener() const - { - return m_consoleLog; - } - - DebuggerLogListener *GetDebuggerListener() const - { - return m_debuggerLog; - } - - static LogManager* GetInstance() - { - return m_logManager; - } - - static void SetInstance(LogManager *logManager) - { - m_logManager = logManager; - } - - static void Init(); - static void Shutdown(); -}; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp new file mode 100644 index 000000000..e79b84604 --- /dev/null +++ b/src/common/logging/backend.cpp @@ -0,0 +1,151 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/log.h" // For _dbg_assert_ + +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/logging/text_formatter.h" + +namespace Log { + +static std::shared_ptr<Logger> global_logger; + +/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. +#define ALL_LOG_CLASSES() \ + CLS(Log) \ + CLS(Common) \ + SUB(Common, Filesystem) \ + SUB(Common, Memory) \ + CLS(Core) \ + SUB(Core, ARM11) \ + CLS(Config) \ + CLS(Debug) \ + SUB(Debug, Emulated) \ + SUB(Debug, GPU) \ + SUB(Debug, Breakpoint) \ + CLS(Kernel) \ + SUB(Kernel, SVC) \ + CLS(Service) \ + SUB(Service, SRV) \ + SUB(Service, FS) \ + SUB(Service, APT) \ + SUB(Service, GSP) \ + SUB(Service, AC) \ + SUB(Service, PTM) \ + SUB(Service, CFG) \ + SUB(Service, DSP) \ + SUB(Service, HID) \ + CLS(HW) \ + SUB(HW, Memory) \ + SUB(HW, GPU) \ + CLS(Frontend) \ + CLS(Render) \ + SUB(Render, Software) \ + SUB(Render, OpenGL) \ + CLS(Loader) + +Logger::Logger() { + // Register logging classes so that they can be queried at runtime + size_t parent_class; + all_classes.reserve((size_t)Class::Count); + +#define CLS(x) \ + all_classes.push_back(Class::x); \ + parent_class = all_classes.size() - 1; +#define SUB(x, y) \ + all_classes.push_back(Class::x##_##y); \ + all_classes[parent_class].num_children += 1; + + ALL_LOG_CLASSES() +#undef CLS +#undef SUB + + // Ensures that ALL_LOG_CLASSES isn't missing any entries. + _dbg_assert_(Log, all_classes.size() == (size_t)Class::Count); +} + +// GetClassName is a macro defined by Windows.h, grrr... +const char* Logger::GetLogClassName(Class log_class) { + switch (log_class) { +#define CLS(x) case Class::x: return #x; +#define SUB(x, y) case Class::x##_##y: return #x "." #y; + ALL_LOG_CLASSES() +#undef CLS +#undef SUB + } + return "Unknown"; +} + +const char* Logger::GetLevelName(Level log_level) { +#define LVL(x) case Level::x: return #x + switch (log_level) { + LVL(Trace); + LVL(Debug); + LVL(Info); + LVL(Warning); + LVL(Error); + LVL(Critical); + } + return "Unknown"; +#undef LVL +} + +void Logger::LogMessage(Entry entry) { + ring_buffer.Push(std::move(entry)); +} + +size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) { + return ring_buffer.BlockingPop(out_buffer, buffer_len); +} + +std::shared_ptr<Logger> InitGlobalLogger() { + global_logger = std::make_shared<Logger>(); + return global_logger; +} + +Entry CreateEntry(Class log_class, Level log_level, + const char* filename, unsigned int line_nr, const char* function, + const char* format, va_list args) { + using std::chrono::steady_clock; + using std::chrono::duration_cast; + + static steady_clock::time_point time_origin = steady_clock::now(); + + std::array<char, 4 * 1024> formatting_buffer; + + Entry entry; + entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); + entry.log_class = log_class; + entry.log_level = log_level; + + snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, line_nr); + entry.location = std::string(formatting_buffer.data()); + + vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args); + entry.message = std::string(formatting_buffer.data()); + + return std::move(entry); +} + +void LogMessage(Class log_class, Level log_level, + const char* filename, unsigned int line_nr, const char* function, + const char* format, ...) { + va_list args; + va_start(args, format); + Entry entry = CreateEntry(log_class, log_level, + filename, line_nr, function, format, args); + va_end(args); + + if (global_logger != nullptr && !global_logger->IsClosed()) { + global_logger->LogMessage(std::move(entry)); + } else { + // Fall back to directly printing to stderr + PrintMessage(entry); + } +} + +} diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h new file mode 100644 index 000000000..ae270efe8 --- /dev/null +++ b/src/common/logging/backend.h @@ -0,0 +1,134 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <cstdarg> +#include <memory> +#include <vector> + +#include "common/concurrent_ring_buffer.h" + +#include "common/logging/log.h" + +namespace Log { + +/** + * A log entry. Log entries are store in a structured format to permit more varied output + * formatting on different frontends, as well as facilitating filtering and aggregation. + */ +struct Entry { + std::chrono::microseconds timestamp; + Class log_class; + Level log_level; + std::string location; + std::string message; + + Entry() = default; + + // TODO(yuriks) Use defaulted move constructors once MSVC supports them +#define MOVE(member) member(std::move(o.member)) + Entry(Entry&& o) + : MOVE(timestamp), MOVE(log_class), MOVE(log_level), + MOVE(location), MOVE(message) + {} +#undef MOVE + + Entry& operator=(const Entry&& o) { +#define MOVE(member) member = std::move(o.member) + MOVE(timestamp); + MOVE(log_class); + MOVE(log_level); + MOVE(location); + MOVE(message); +#undef MOVE + return *this; + } +}; + +struct ClassInfo { + Class log_class; + + /** + * Total number of (direct or indirect) sub classes this class has. If any, they follow in + * sequence after this class in the class list. + */ + unsigned int num_children = 0; + + ClassInfo(Class log_class) : log_class(log_class) {} +}; + +/** + * Logging management class. This class has the dual purpose of acting as an exchange point between + * the logging clients and the log outputter, as well as containing reflection info about available + * log classes. + */ +class Logger { +private: + using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>; + +public: + static const size_t QUEUE_CLOSED = Buffer::QUEUE_CLOSED; + + Logger(); + + /** + * Returns a list of all vector classes and subclasses. The sequence returned is a pre-order of + * classes and subclasses, which together with the `num_children` field in ClassInfo, allows + * you to recover the hierarchy. + */ + const std::vector<ClassInfo>& GetClasses() const { return all_classes; } + + /** + * Returns the name of the passed log class as a C-string. Subclasses are separated by periods + * instead of underscores as in the enumeration. + */ + static const char* GetLogClassName(Class log_class); + + /** + * Returns the name of the passed log level as a C-string. + */ + static const char* GetLevelName(Level log_level); + + /** + * Appends a messages to the log buffer. + * @note This function is thread safe. + */ + void LogMessage(Entry entry); + + /** + * Retrieves a batch of messages from the log buffer, blocking until they are available. + * @note This function is thread safe. + * + * @param out_buffer Destination buffer that will receive the log entries. + * @param buffer_len The maximum size of `out_buffer`. + * @return The number of entries stored. In case the logger is shutting down, `QUEUE_CLOSED` is + * returned, no entries are stored and the logger should shutdown. + */ + size_t GetEntries(Entry* out_buffer, size_t buffer_len); + + /** + * Initiates a shutdown of the logger. This will indicate to log output clients that they + * should shutdown. + */ + void Close() { ring_buffer.Close(); } + + /** + * Returns true if Close() has already been called on the Logger. + */ + bool IsClosed() const { return ring_buffer.IsClosed(); } + +private: + Buffer ring_buffer; + std::vector<ClassInfo> all_classes; +}; + +/// Creates a log entry by formatting the given source location, and message. +Entry CreateEntry(Class log_class, Level log_level, + const char* filename, unsigned int line_nr, const char* function, + const char* format, va_list args); +/// Initializes the default Logger. +std::shared_ptr<Logger> InitGlobalLogger(); + +} diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp new file mode 100644 index 000000000..0cf9b05e7 --- /dev/null +++ b/src/common/logging/filter.cpp @@ -0,0 +1,132 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> + +#include "common/logging/filter.h" +#include "common/logging/backend.h" +#include "common/string_util.h" + +namespace Log { + +Filter::Filter(Level default_level) { + ResetAll(default_level); +} + +void Filter::ResetAll(Level level) { + class_levels.fill(level); +} + +void Filter::SetClassLevel(Class log_class, Level level) { + class_levels[static_cast<size_t>(log_class)] = level; +} + +void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) { + const size_t log_class_i = static_cast<size_t>(log_class.log_class); + + const size_t begin = log_class_i + 1; + const size_t end = begin + log_class.num_children; + for (size_t i = begin; begin < end; ++i) { + class_levels[i] = level; + } +} + +void Filter::ParseFilterString(const std::string& filter_str) { + auto clause_begin = filter_str.cbegin(); + while (clause_begin != filter_str.cend()) { + auto clause_end = std::find(clause_begin, filter_str.cend(), ' '); + + // If clause isn't empty + if (clause_end != clause_begin) { + ParseFilterRule(clause_begin, clause_end); + } + + if (clause_end != filter_str.cend()) { + // Skip over the whitespace + ++clause_end; + } + clause_begin = clause_end; + } +} + +template <typename It> +static Level GetLevelByName(const It begin, const It end) { + for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) { + const char* level_name = Logger::GetLevelName(static_cast<Level>(i)); + if (Common::ComparePartialString(begin, end, level_name)) { + return static_cast<Level>(i); + } + } + return Level::Count; +} + +template <typename It> +static Class GetClassByName(const It begin, const It end) { + for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) { + const char* level_name = Logger::GetLogClassName(static_cast<Class>(i)); + if (Common::ComparePartialString(begin, end, level_name)) { + return static_cast<Class>(i); + } + } + return Class::Count; +} + +template <typename InputIt, typename T> +static InputIt find_last(InputIt begin, const InputIt end, const T& value) { + auto match = end; + while (begin != end) { + auto new_match = std::find(begin, end, value); + if (new_match != end) { + match = new_match; + ++new_match; + } + begin = new_match; + } + return match; +} + +bool Filter::ParseFilterRule(const std::string::const_iterator begin, + const std::string::const_iterator end) { + auto level_separator = std::find(begin, end, ':'); + if (level_separator == end) { + LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s", + std::string(begin, end).c_str()); + return false; + } + + const Level level = GetLevelByName(level_separator + 1, end); + if (level == Level::Count) { + LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str()); + return false; + } + + if (Common::ComparePartialString(begin, level_separator, "*")) { + ResetAll(level); + return true; + } + + auto class_name_end = find_last(begin, level_separator, '.'); + if (class_name_end != level_separator && + !Common::ComparePartialString(class_name_end + 1, level_separator, "*")) { + class_name_end = level_separator; + } + + const Class log_class = GetClassByName(begin, class_name_end); + if (log_class == Class::Count) { + LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str()); + return false; + } + + if (class_name_end == level_separator) { + SetClassLevel(log_class, level); + } + SetSubclassesLevel(log_class, level); + return true; +} + +bool Filter::CheckMessage(Class log_class, Level level) const { + return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]); +} + +} diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h new file mode 100644 index 000000000..32b14b159 --- /dev/null +++ b/src/common/logging/filter.h @@ -0,0 +1,63 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <array> +#include <string> + +#include "common/logging/log.h" + +namespace Log { + +struct ClassInfo; + +/** + * Implements a log message filter which allows different log classes to have different minimum + * severity levels. The filter can be changed at runtime and can be parsed from a string to allow + * editing via the interface or loading from a configuration file. + */ +class Filter { +public: + /// Initializes the filter with all classes having `default_level` as the minimum level. + Filter(Level default_level); + + /// Resets the filter so that all classes have `level` as the minimum displayed level. + void ResetAll(Level level); + /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. + void SetClassLevel(Class log_class, Level level); + /** + * Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class` + * itself is not changed. + */ + void SetSubclassesLevel(const ClassInfo& log_class, Level level); + + /** + * Parses a filter string and applies it to this filter. + * + * A filter string consists of a space-separated list of filter rules, each of the format + * `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods. + * A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and + * can be used to apply a rule to all classes or to all subclasses of a class without affecting + * the parent class. `<level>` a severity level name which will be set as the minimum logging + * level of the matched classes. Rules are applied left to right, with each rule overriding + * previous ones in the sequence. + * + * A few examples of filter rules: + * - `*:Info` -- Resets the level of all classes to Info. + * - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT, + * etc.) to Info. + * - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the + * level of Service unchanged. + * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. + */ + void ParseFilterString(const std::string& filter_str); + bool ParseFilterRule(const std::string::const_iterator start, const std::string::const_iterator end); + + /// Matches class/level combination against the filter, returning true if it passed. + bool CheckMessage(Class log_class, Level level) const; + +private: + std::array<Level, (size_t)Class::Count> class_levels; +}; + +} diff --git a/src/common/logging/log.h b/src/common/logging/log.h new file mode 100644 index 000000000..1eec34fbb --- /dev/null +++ b/src/common/logging/log.h @@ -0,0 +1,115 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <cassert> +#include <chrono> +#include <string> + +#include "common/common_types.h" + +namespace Log { + +/// Specifies the severity or level of detail of the log message. +enum class Level : u8 { + Trace, ///< Extremely detailed and repetitive debugging information that is likely to + /// pollute logs. + Debug, ///< Less detailed debugging information. + Info, ///< Status information from important points during execution. + Warning, ///< Minor or potential problems found during execution of a task. + Error, ///< Major problems found during execution of a task that prevent it from being + /// completed. + Critical, ///< Major problems during execution that threathen the stability of the entire + /// application. + + Count ///< Total number of logging levels +}; + +typedef u8 ClassType; + +/** + * Specifies the sub-system that generated the log message. + * + * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in log.cpp. + */ +enum class Class : ClassType { + Log, ///< Messages about the log system itself + Common, ///< Library routines + Common_Filesystem, ///< Filesystem interface library + Common_Memory, ///< Memory mapping and management functions + Core, ///< LLE emulation core + Core_ARM11, ///< ARM11 CPU core + Config, ///< Emulator configuration (including commandline) + Debug, ///< Debugging tools + Debug_Emulated, ///< Debug messages from the emulated programs + Debug_GPU, ///< GPU debugging tools + Debug_Breakpoint, ///< Logging breakpoints and watchpoints + Kernel, ///< The HLE implementation of the CTR kernel + Kernel_SVC, ///< Kernel system calls + Service, ///< HLE implementation of system services. Each major service + /// should have its own subclass. + Service_SRV, ///< The SRV (Service Directory) implementation + Service_FS, ///< The FS (Filesystem) service implementation + Service_APT, ///< The APT (Applets) service + Service_GSP, ///< The GSP (GPU control) service + Service_AC, ///< The AC (WiFi status) service + Service_PTM, ///< The PTM (Power status & misc.) service + Service_CFG, ///< The CFG (Configuration) service + Service_DSP, ///< The DSP (DSP control) service + Service_HID, ///< The HID (User input) service + HW, ///< Low-level hardware emulation + HW_Memory, ///< Memory-map and address translation + HW_GPU, ///< GPU control emulation + Frontend, ///< Emulator UI + Render, ///< Emulator video output and hardware acceleration + Render_Software, ///< Software renderer backend + Render_OpenGL, ///< OpenGL backend + Loader, ///< ROM loader + + Count ///< Total number of logging classes +}; + +/** + * Level below which messages are simply discarded without buffering regardless of the display + * settings. + */ +const Level MINIMUM_LEVEL = +#ifdef _DEBUG + Level::Trace; +#else + Level::Debug; +#endif + +/** + * Logs a message to the global logger. This proxy exists to avoid exposing the details of the + * Logger class, including the ConcurrentRingBuffer template, to all files that desire to log + * messages, reducing unecessary recompilations. + */ +void LogMessage(Class log_class, Level log_level, + const char* filename, unsigned int line_nr, const char* function, +#ifdef _MSC_VER + _Printf_format_string_ +#endif + const char* format, ...) +#ifdef __GNUC__ + __attribute__((format(printf, 6, 7))) +#endif + ; + +} // namespace Log + +#define LOG_GENERIC(log_class, log_level, ...) \ + do { \ + if (::Log::Level::log_level >= ::Log::MINIMUM_LEVEL) \ + ::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \ + __FILE__, __LINE__, __func__, __VA_ARGS__); \ + } while (0) + +#define LOG_TRACE( log_class, ...) LOG_GENERIC(log_class, Trace, __VA_ARGS__) +#define LOG_DEBUG( log_class, ...) LOG_GENERIC(log_class, Debug, __VA_ARGS__) +#define LOG_INFO( log_class, ...) LOG_GENERIC(log_class, Info, __VA_ARGS__) +#define LOG_WARNING( log_class, ...) LOG_GENERIC(log_class, Warning, __VA_ARGS__) +#define LOG_ERROR( log_class, ...) LOG_GENERIC(log_class, Error, __VA_ARGS__) +#define LOG_CRITICAL(log_class, ...) LOG_GENERIC(log_class, Critical, __VA_ARGS__) diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp new file mode 100644 index 000000000..3fe435346 --- /dev/null +++ b/src/common/logging/text_formatter.cpp @@ -0,0 +1,126 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <array> +#include <cstdio> + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# include <Windows.h> +#endif + +#include "common/logging/backend.h" +#include "common/logging/filter.h" +#include "common/logging/log.h" +#include "common/logging/text_formatter.h" + +#include "common/string_util.h" + +namespace Log { + +// TODO(bunnei): This should be moved to a generic path manipulation library +const char* TrimSourcePath(const char* path, const char* root) { + const char* p = path; + + while (*p != '\0') { + const char* next_slash = p; + while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') { + ++next_slash; + } + + bool is_src = Common::ComparePartialString(p, next_slash, root); + p = next_slash; + + if (*p != '\0') { + ++p; + } + if (is_src) { + path = p; + } + } + return path; +} + +void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) { + unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000); + unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); + + const char* class_name = Logger::GetLogClassName(entry.log_class); + const char* level_name = Logger::GetLevelName(entry.log_level); + + snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", + time_seconds, time_fractional, class_name, level_name, + TrimSourcePath(entry.location.c_str()), entry.message.c_str()); +} + +static void ChangeConsoleColor(Level level) { +#ifdef _WIN32 + static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); + + WORD color = 0; + switch (level) { + case Level::Trace: // Grey + color = FOREGROUND_INTENSITY; break; + case Level::Debug: // Cyan + color = FOREGROUND_GREEN | FOREGROUND_BLUE; break; + case Level::Info: // Bright gray + color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break; + case Level::Warning: // Bright yellow + color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; + case Level::Error: // Bright red + color = FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case Level::Critical: // Bright magenta + color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; + } + + SetConsoleTextAttribute(console_handle, color); +#else +#define ESC "\x1b" + const char* color = ""; + switch (level) { + case Level::Trace: // Grey + color = ESC "[1;30m"; break; + case Level::Debug: // Cyan + color = ESC "[0;36m"; break; + case Level::Info: // Bright gray + color = ESC "[0;37m"; break; + case Level::Warning: // Bright yellow + color = ESC "[1;33m"; break; + case Level::Error: // Bright red + color = ESC "[1;31m"; break; + case Level::Critical: // Bright magenta + color = ESC "[1;35m"; break; + } +#undef ESC + + fputs(color, stderr); +#endif +} + +void PrintMessage(const Entry& entry) { + ChangeConsoleColor(entry.log_level); + std::array<char, 4 * 1024> format_buffer; + FormatLogMessage(entry, format_buffer.data(), format_buffer.size()); + fputs(format_buffer.data(), stderr); + fputc('\n', stderr); +} + +void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) { + std::array<Entry, 256> entry_buffer; + + while (true) { + size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size()); + if (num_entries == Logger::QUEUE_CLOSED) { + break; + } + for (size_t i = 0; i < num_entries; ++i) { + const Entry& entry = entry_buffer[i]; + if (filter->CheckMessage(entry.log_class, entry.log_level)) { + PrintMessage(entry); + } + } + } +} + +} diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h new file mode 100644 index 000000000..d7e298e28 --- /dev/null +++ b/src/common/logging/text_formatter.h @@ -0,0 +1,39 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <memory> + +namespace Log { + +class Logger; +struct Entry; +class Filter; + +/** + * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's + * intended to be used to strip a system-specific build directory from the `__FILE__` macro, + * leaving only the path relative to the sources root. + * + * @param path The input file path as a null-terminated string + * @param root The name of the root source directory as a null-terminated string. Path up to and + * including the last occurence of this name will be stripped + * @return A pointer to the same string passed as `path`, but starting at the trimmed portion + */ +const char* TrimSourcePath(const char* path, const char* root = "src"); + +/// Formats a log entry into the provided text buffer. +void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len); +/// Formats and prints a log entry to stderr. +void PrintMessage(const Entry& entry); + +/** + * Logging loop that repeatedly reads messages from the provided logger and prints them to the + * console. It is the baseline barebones log outputter. + */ +void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter); + +} diff --git a/src/common/mem_arena.cpp b/src/common/mem_arena.cpp index 7d4fda0e2..9904d2472 100644 --- a/src/common/mem_arena.cpp +++ b/src/common/mem_arena.cpp @@ -71,7 +71,7 @@ int ashmem_create_region(const char *name, size_t size) return fd; error: - ERROR_LOG(MEMMAP, "NASTY ASHMEM ERROR: ret = %08x", ret); + LOG_ERROR(Common_Memory, "NASTY ASHMEM ERROR: ret = %08x", ret); close(fd); return ret; } @@ -130,7 +130,7 @@ void MemArena::GrabLowMemSpace(size_t size) // Note that it appears that ashmem is pinned by default, so no need to pin. if (fd < 0) { - ERROR_LOG(MEMMAP, "Failed to grab ashmem space of size: %08x errno: %d", (int)size, (int)(errno)); + LOG_ERROR(Common_Memory, "Failed to grab ashmem space of size: %08x errno: %d", (int)size, (int)(errno)); return; } #else @@ -148,12 +148,12 @@ void MemArena::GrabLowMemSpace(size_t size) } else if (errno != EEXIST) { - ERROR_LOG(MEMMAP, "shm_open failed: %s", strerror(errno)); + LOG_ERROR(Common_Memory, "shm_open failed: %s", strerror(errno)); return; } } if (ftruncate(fd, size) < 0) - ERROR_LOG(MEMMAP, "Failed to allocate low memory space"); + LOG_ERROR(Common_Memory, "Failed to allocate low memory space"); #endif } @@ -197,7 +197,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) if (retval == MAP_FAILED) { - NOTICE_LOG(MEMMAP, "mmap failed"); + LOG_ERROR(Common_Memory, "mmap failed"); return nullptr; } return retval; @@ -423,7 +423,7 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena base = (u8 *)base_addr; if (Memory_TryBase(base, views, num_views, flags, arena)) { - INFO_LOG(MEMMAP, "Found valid memory base at %p after %i tries.", base, base_attempts); + LOG_DEBUG(Common_Memory, "Found valid memory base at %p after %i tries.", base, base_attempts); base_attempts = 0; break; } @@ -442,7 +442,7 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena u8 *base = MemArena::Find4GBBase(); if (!Memory_TryBase(base, views, num_views, flags, arena)) { - ERROR_LOG(MEMMAP, "MemoryMap_Setup: Failed finding a memory base."); + LOG_ERROR(Common_Memory, "MemoryMap_Setup: Failed finding a memory base."); PanicAlert("MemoryMap_Setup: Failed finding a memory base."); return 0; } diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp index 93da5500b..ca8a2a9e4 100644 --- a/src/common/memory_util.cpp +++ b/src/common/memory_util.cpp @@ -109,7 +109,7 @@ void* AllocateAlignedMemory(size_t size,size_t alignment) ptr = memalign(alignment, size); #else if (posix_memalign(&ptr, alignment, size) != 0) - ERROR_LOG(MEMMAP, "Failed to allocate aligned memory"); + LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); #endif #endif diff --git a/src/common/msg_handler.cpp b/src/common/msg_handler.cpp index b3556aaa8..7ffedc45a 100644 --- a/src/common/msg_handler.cpp +++ b/src/common/msg_handler.cpp @@ -75,7 +75,7 @@ bool MsgAlert(bool yes_no, int Style, const char* format, ...) Common::CharArrayFromFormatV(buffer, sizeof(buffer)-1, str_translator(format).c_str(), args); va_end(args); - ERROR_LOG(MASTER_LOG, "%s: %s", caption.c_str(), buffer); + LOG_INFO(Common, "%s: %s", caption.c_str(), buffer); // Don't ignore questions, especially AskYesNo, PanicYesNo could be ignored if (msg_handler && (AlertEnabled || Style == QUESTION || Style == CRITICAL)) diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h new file mode 100644 index 000000000..1d3e59319 --- /dev/null +++ b/src/common/scope_exit.h @@ -0,0 +1,37 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +namespace detail { + template <typename Func> + struct ScopeExitHelper { + explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {} + ~ScopeExitHelper() { func(); } + + Func func; + }; + + template <typename Func> + ScopeExitHelper<Func> ScopeExit(Func&& func) { return ScopeExitHelper<Func>(std::move(func)); } +} + +/** + * This macro allows you to conveniently specify a block of code that will run on scope exit. Handy + * for doing ad-hoc clean-up tasks in a function with multiple returns. + * + * Example usage: + * \code + * const int saved_val = g_foo; + * g_foo = 55; + * SCOPE_EXIT({ g_foo = saved_val; }); + * + * if (Bar()) { + * return 0; + * } else { + * return 20; + * } + * \endcode + */ +#define SCOPE_EXIT(body) auto scope_exit_helper_##__LINE__ = detail::ScopeExit([&]() body) diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 7a8274a91..6d9612fb5 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -107,7 +107,7 @@ std::string StringFromFormat(const char* format, ...) #else va_start(args, format); if (vasprintf(&buf, format, args) < 0) - ERROR_LOG(COMMON, "Unable to allocate memory for string"); + LOG_ERROR(Common, "Unable to allocate memory for string"); va_end(args); std::string temp = buf; @@ -475,7 +475,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& iconv_t const conv_desc = iconv_open("UTF-8", fromcode); if ((iconv_t)(-1) == conv_desc) { - ERROR_LOG(COMMON, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno)); + LOG_ERROR(Common, "Iconv initialization failure [%s]: %s", fromcode, strerror(errno)); iconv_close(conv_desc); return {}; } @@ -510,7 +510,7 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& } else { - ERROR_LOG(COMMON, "iconv failure [%s]: %s", fromcode, strerror(errno)); + LOG_ERROR(Common, "iconv failure [%s]: %s", fromcode, strerror(errno)); break; } } @@ -531,7 +531,7 @@ std::u16string UTF8ToUTF16(const std::string& input) iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8"); if ((iconv_t)(-1) == conv_desc) { - ERROR_LOG(COMMON, "Iconv initialization failure [UTF-8]: %s", strerror(errno)); + LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: %s", strerror(errno)); iconv_close(conv_desc); return {}; } @@ -566,7 +566,7 @@ std::u16string UTF8ToUTF16(const std::string& input) } else { - ERROR_LOG(COMMON, "iconv failure [UTF-8]: %s", strerror(errno)); + LOG_ERROR(Common, "iconv failure [UTF-8]: %s", strerror(errno)); break; } } diff --git a/src/common/string_util.h b/src/common/string_util.h index ae5bbadad..7d75691b1 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -115,4 +115,19 @@ inline std::string UTF8ToTStr(const std::string& str) #endif +/** + * Compares the string defined by the range [`begin`, `end`) to the null-terminated C-string + * `other` for equality. + */ +template <typename InIt> +bool ComparePartialString(InIt begin, InIt end, const char* other) { + for (; begin != end && *other != '\0'; ++begin, ++other) { + if (*begin != *other) { + return false; + } + } + // Only return true if both strings finished at the same point + return (begin == end) == (*other == '\0'); +} + } diff --git a/src/common/thread.h b/src/common/thread.h index be9b5cbe2..8c36d3f07 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -21,6 +21,7 @@ //for gettimeofday and struct time(spec|val) #include <time.h> #include <sys/time.h> +#include <unistd.h> #endif namespace Common |