diff options
| -rw-r--r-- | src/citra/citra.cpp | 6 | ||||
| -rw-r--r-- | src/citra_qt/main.cpp | 6 | ||||
| -rw-r--r-- | src/common/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/common/assert.h | 10 | ||||
| -rw-r--r-- | src/common/concurrent_ring_buffer.h | 163 | ||||
| -rw-r--r-- | src/common/logging/backend.cpp | 56 | ||||
| -rw-r--r-- | src/common/logging/backend.h | 92 | ||||
| -rw-r--r-- | src/common/logging/filter.cpp | 41 | ||||
| -rw-r--r-- | src/common/logging/filter.h | 20 | ||||
| -rw-r--r-- | src/common/logging/log.h | 6 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.cpp | 19 | ||||
| -rw-r--r-- | src/common/logging/text_formatter.h | 8 |
12 files changed, 35 insertions, 393 deletions
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 1d7e7f270..ca93d5b91 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp | |||
| @@ -20,14 +20,8 @@ | |||
| 20 | 20 | ||
| 21 | /// Application entry point | 21 | /// Application entry point |
| 22 | int main(int argc, char **argv) { | 22 | int main(int argc, char **argv) { |
| 23 | std::shared_ptr<Log::Logger> logger = Log::InitGlobalLogger(); | ||
| 24 | Log::Filter log_filter(Log::Level::Debug); | 23 | Log::Filter log_filter(Log::Level::Debug); |
| 25 | Log::SetFilter(&log_filter); | 24 | Log::SetFilter(&log_filter); |
| 26 | std::thread logging_thread(Log::TextLoggingLoop, logger); | ||
| 27 | SCOPE_EXIT({ | ||
| 28 | logger->Close(); | ||
| 29 | logging_thread.join(); | ||
| 30 | }); | ||
| 31 | 25 | ||
| 32 | if (argc < 2) { | 26 | if (argc < 2) { |
| 33 | LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); | 27 | LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); |
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 7b028e323..24506deab 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp | |||
| @@ -352,14 +352,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) | |||
| 352 | 352 | ||
| 353 | int main(int argc, char* argv[]) | 353 | int main(int argc, char* argv[]) |
| 354 | { | 354 | { |
| 355 | std::shared_ptr<Log::Logger> logger = Log::InitGlobalLogger(); | ||
| 356 | Log::Filter log_filter(Log::Level::Info); | 355 | Log::Filter log_filter(Log::Level::Info); |
| 357 | Log::SetFilter(&log_filter); | 356 | Log::SetFilter(&log_filter); |
| 358 | std::thread logging_thread(Log::TextLoggingLoop, logger); | ||
| 359 | SCOPE_EXIT({ | ||
| 360 | logger->Close(); | ||
| 361 | logging_thread.join(); | ||
| 362 | }); | ||
| 363 | 357 | ||
| 364 | QApplication::setAttribute(Qt::AA_X11InitThreads); | 358 | QApplication::setAttribute(Qt::AA_X11InitThreads); |
| 365 | QApplication app(argc, argv); | 359 | QApplication app(argc, argv); |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f8fc6450f..dbaaac77b 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -27,7 +27,6 @@ set(HEADERS | |||
| 27 | common_funcs.h | 27 | common_funcs.h |
| 28 | common_paths.h | 28 | common_paths.h |
| 29 | common_types.h | 29 | common_types.h |
| 30 | concurrent_ring_buffer.h | ||
| 31 | cpu_detect.h | 30 | cpu_detect.h |
| 32 | debug_interface.h | 31 | debug_interface.h |
| 33 | emu_window.h | 32 | emu_window.h |
diff --git a/src/common/assert.h b/src/common/assert.h index 4f26c63e9..7b7d8bf28 100644 --- a/src/common/assert.h +++ b/src/common/assert.h | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <cstdlib> | 8 | #include <cstdlib> |
| 9 | 9 | ||
| 10 | #include "common/common_funcs.h" | 10 | #include "common/common_funcs.h" |
| 11 | #include "common/logging/log.h" | ||
| 11 | 12 | ||
| 12 | // For asserts we'd like to keep all the junk executed when an assert happens away from the | 13 | // For asserts we'd like to keep all the junk executed when an assert happens away from the |
| 13 | // important code in the function. One way of doing this is to put all the relevant code inside a | 14 | // important code in the function. One way of doing this is to put all the relevant code inside a |
| @@ -28,19 +29,14 @@ static void assert_noinline_call(const Fn& fn) { | |||
| 28 | exit(1); // Keeps GCC's mouth shut about this actually returning | 29 | exit(1); // Keeps GCC's mouth shut about this actually returning |
| 29 | } | 30 | } |
| 30 | 31 | ||
| 31 | // TODO (yuriks) allow synchronous logging so we don't need printf | ||
| 32 | #define ASSERT(_a_) \ | 32 | #define ASSERT(_a_) \ |
| 33 | do if (!(_a_)) { assert_noinline_call([] { \ | 33 | do if (!(_a_)) { assert_noinline_call([] { \ |
| 34 | fprintf(stderr, "Assertion Failed!\n\n Line: %d\n File: %s\n Time: %s\n", \ | 34 | LOG_CRITICAL(Debug, "Assertion Failed!"); \ |
| 35 | __LINE__, __FILE__, __TIME__); \ | ||
| 36 | }); } while (0) | 35 | }); } while (0) |
| 37 | 36 | ||
| 38 | #define ASSERT_MSG(_a_, ...) \ | 37 | #define ASSERT_MSG(_a_, ...) \ |
| 39 | do if (!(_a_)) { assert_noinline_call([&] { \ | 38 | do if (!(_a_)) { assert_noinline_call([&] { \ |
| 40 | fprintf(stderr, "Assertion Failed!\n\n Line: %d\n File: %s\n Time: %s\n", \ | 39 | LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); \ |
| 41 | __LINE__, __FILE__, __TIME__); \ | ||
| 42 | fprintf(stderr, __VA_ARGS__); \ | ||
| 43 | fprintf(stderr, "\n"); \ | ||
| 44 | }); } while (0) | 40 | }); } while (0) |
| 45 | 41 | ||
| 46 | #define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!") | 42 | #define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!") |
diff --git a/src/common/concurrent_ring_buffer.h b/src/common/concurrent_ring_buffer.h deleted file mode 100644 index c5889513a..000000000 --- a/src/common/concurrent_ring_buffer.h +++ /dev/null | |||
| @@ -1,163 +0,0 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <condition_variable> | ||
| 9 | #include <cstdint> | ||
| 10 | #include <mutex> | ||
| 11 | #include <thread> | ||
| 12 | |||
| 13 | #include "common/common_types.h" // for NonCopyable | ||
| 14 | |||
| 15 | namespace Common { | ||
| 16 | |||
| 17 | /** | ||
| 18 | * A MPMC (Multiple-Producer Multiple-Consumer) concurrent ring buffer. This data structure permits | ||
| 19 | * multiple threads to push and pop from a queue of bounded size. | ||
| 20 | */ | ||
| 21 | template <typename T, size_t ArraySize> | ||
| 22 | class ConcurrentRingBuffer : private NonCopyable { | ||
| 23 | public: | ||
| 24 | /// Value returned by the popping functions when the queue has been closed. | ||
| 25 | static const size_t QUEUE_CLOSED = -1; | ||
| 26 | |||
| 27 | ConcurrentRingBuffer() {} | ||
| 28 | |||
| 29 | ~ConcurrentRingBuffer() { | ||
| 30 | // If for whatever reason the queue wasn't completely drained, destroy the left over items. | ||
| 31 | for (size_t i = reader_index, end = writer_index; i != end; i = (i + 1) % ArraySize) { | ||
| 32 | Data()[i].~T(); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Pushes a value to the queue. If the queue is full, this method will block. Does nothing if | ||
| 38 | * the queue is closed. | ||
| 39 | */ | ||
| 40 | void Push(T val) { | ||
| 41 | std::unique_lock<std::mutex> lock(mutex); | ||
| 42 | if (closed) { | ||
| 43 | return; | ||
| 44 | } | ||
| 45 | |||
| 46 | // If the buffer is full, wait | ||
| 47 | writer.wait(lock, [&]{ | ||
| 48 | return (writer_index + 1) % ArraySize != reader_index; | ||
| 49 | }); | ||
| 50 | |||
| 51 | T* item = &Data()[writer_index]; | ||
| 52 | new (item) T(std::move(val)); | ||
| 53 | |||
| 54 | writer_index = (writer_index + 1) % ArraySize; | ||
| 55 | |||
| 56 | // Wake up waiting readers | ||
| 57 | lock.unlock(); | ||
| 58 | reader.notify_one(); | ||
| 59 | } | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will not | ||
| 63 | * block, and might return 0 values if there are no elements in the queue when it is called. | ||
| 64 | * | ||
| 65 | * @return The number of elements stored in `dest`. If the queue has been closed, returns | ||
| 66 | * `QUEUE_CLOSED`. | ||
| 67 | */ | ||
| 68 | size_t Pop(T* dest, size_t dest_len) { | ||
| 69 | std::unique_lock<std::mutex> lock(mutex); | ||
| 70 | if (closed && !CanRead()) { | ||
| 71 | return QUEUE_CLOSED; | ||
| 72 | } | ||
| 73 | return PopInternal(dest, dest_len); | ||
| 74 | } | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Pops up to `dest_len` items from the queue, storing them in `dest`. This function will block | ||
| 78 | * if there are no elements in the queue when it is called. | ||
| 79 | * | ||
| 80 | * @return The number of elements stored in `dest`. If the queue has been closed, returns | ||
| 81 | * `QUEUE_CLOSED`. | ||
| 82 | */ | ||
| 83 | size_t BlockingPop(T* dest, size_t dest_len) { | ||
| 84 | std::unique_lock<std::mutex> lock(mutex); | ||
| 85 | if (closed && !CanRead()) { | ||
| 86 | return QUEUE_CLOSED; | ||
| 87 | } | ||
| 88 | |||
| 89 | while (!CanRead()) { | ||
| 90 | reader.wait(lock); | ||
| 91 | if (closed && !CanRead()) { | ||
| 92 | return QUEUE_CLOSED; | ||
| 93 | } | ||
| 94 | } | ||
| 95 | DEBUG_ASSERT(CanRead()); | ||
| 96 | return PopInternal(dest, dest_len); | ||
| 97 | } | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Closes the queue. After calling this method, `Push` operations won't have any effect, and | ||
| 101 | * `PopMany` and `PopManyBlock` will start returning `QUEUE_CLOSED`. This is intended to allow | ||
| 102 | * a graceful shutdown of all consumers. | ||
| 103 | */ | ||
| 104 | void Close() { | ||
| 105 | std::unique_lock<std::mutex> lock(mutex); | ||
| 106 | closed = true; | ||
| 107 | // We need to wake up any reader that are waiting for an item that will never come. | ||
| 108 | lock.unlock(); | ||
| 109 | reader.notify_all(); | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Returns true if `Close()` has been called. | ||
| 113 | bool IsClosed() const { | ||
| 114 | return closed; | ||
| 115 | } | ||
| 116 | |||
| 117 | private: | ||
| 118 | size_t PopInternal(T* dest, size_t dest_len) { | ||
| 119 | size_t output_count = 0; | ||
| 120 | while (output_count < dest_len && CanRead()) { | ||
| 121 | DEBUG_ASSERT(CanRead()); | ||
| 122 | |||
| 123 | T* item = &Data()[reader_index]; | ||
| 124 | T out_val = std::move(*item); | ||
| 125 | item->~T(); | ||
| 126 | |||
| 127 | size_t prev_index = (reader_index + ArraySize - 1) % ArraySize; | ||
| 128 | reader_index = (reader_index + 1) % ArraySize; | ||
| 129 | if (writer_index == prev_index) { | ||
| 130 | writer.notify_one(); | ||
| 131 | } | ||
| 132 | dest[output_count++] = std::move(out_val); | ||
| 133 | } | ||
| 134 | return output_count; | ||
| 135 | } | ||
| 136 | |||
| 137 | bool CanRead() const { | ||
| 138 | return reader_index != writer_index; | ||
| 139 | } | ||
| 140 | |||
| 141 | T* Data() { | ||
| 142 | return static_cast<T*>(static_cast<void*>(&storage)); | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Storage for entries | ||
| 146 | typename std::aligned_storage<ArraySize * sizeof(T), | ||
| 147 | std::alignment_of<T>::value>::type storage; | ||
| 148 | |||
| 149 | /// Data is valid in the half-open interval [reader, writer). If they are `QUEUE_CLOSED` then the | ||
| 150 | /// queue has been closed. | ||
| 151 | size_t writer_index = 0, reader_index = 0; | ||
| 152 | // True if the queue has been closed. | ||
| 153 | bool closed = false; | ||
| 154 | |||
| 155 | /// Mutex that protects the entire data structure. | ||
| 156 | std::mutex mutex; | ||
| 157 | /// Signaling wakes up reader which is waiting for storage to be non-empty. | ||
| 158 | std::condition_variable reader; | ||
| 159 | /// Signaling wakes up writer which is waiting for storage to be non-full. | ||
| 160 | std::condition_variable writer; | ||
| 161 | }; | ||
| 162 | |||
| 163 | } // namespace | ||
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 7d3534a43..bd2c6a153 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp | |||
| @@ -3,17 +3,17 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <array> | ||
| 7 | #include <cstdio> | ||
| 6 | 8 | ||
| 7 | #include "common/assert.h" | 9 | #include "common/common_funcs.h" // snprintf compatibility define |
| 8 | |||
| 9 | #include "common/logging/backend.h" | 10 | #include "common/logging/backend.h" |
| 11 | #include "common/logging/filter.h" | ||
| 10 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 11 | #include "common/logging/text_formatter.h" | 13 | #include "common/logging/text_formatter.h" |
| 12 | 14 | ||
| 13 | namespace Log { | 15 | namespace Log { |
| 14 | 16 | ||
| 15 | static std::shared_ptr<Logger> global_logger; | ||
| 16 | |||
| 17 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. | 17 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. |
| 18 | #define ALL_LOG_CLASSES() \ | 18 | #define ALL_LOG_CLASSES() \ |
| 19 | CLS(Log) \ | 19 | CLS(Log) \ |
| @@ -55,28 +55,8 @@ static std::shared_ptr<Logger> global_logger; | |||
| 55 | SUB(Render, OpenGL) \ | 55 | SUB(Render, OpenGL) \ |
| 56 | CLS(Loader) | 56 | CLS(Loader) |
| 57 | 57 | ||
| 58 | Logger::Logger() { | ||
| 59 | // Register logging classes so that they can be queried at runtime | ||
| 60 | size_t parent_class; | ||
| 61 | all_classes.reserve((size_t)Class::Count); | ||
| 62 | |||
| 63 | #define CLS(x) \ | ||
| 64 | all_classes.push_back(Class::x); \ | ||
| 65 | parent_class = all_classes.size() - 1; | ||
| 66 | #define SUB(x, y) \ | ||
| 67 | all_classes.push_back(Class::x##_##y); \ | ||
| 68 | all_classes[parent_class].num_children += 1; | ||
| 69 | |||
| 70 | ALL_LOG_CLASSES() | ||
| 71 | #undef CLS | ||
| 72 | #undef SUB | ||
| 73 | |||
| 74 | // Ensures that ALL_LOG_CLASSES isn't missing any entries. | ||
| 75 | DEBUG_ASSERT(all_classes.size() == (size_t)Class::Count); | ||
| 76 | } | ||
| 77 | |||
| 78 | // GetClassName is a macro defined by Windows.h, grrr... | 58 | // GetClassName is a macro defined by Windows.h, grrr... |
| 79 | const char* Logger::GetLogClassName(Class log_class) { | 59 | const char* GetLogClassName(Class log_class) { |
| 80 | switch (log_class) { | 60 | switch (log_class) { |
| 81 | #define CLS(x) case Class::x: return #x; | 61 | #define CLS(x) case Class::x: return #x; |
| 82 | #define SUB(x, y) case Class::x##_##y: return #x "." #y; | 62 | #define SUB(x, y) case Class::x##_##y: return #x "." #y; |
| @@ -87,7 +67,7 @@ const char* Logger::GetLogClassName(Class log_class) { | |||
| 87 | return "Unknown"; | 67 | return "Unknown"; |
| 88 | } | 68 | } |
| 89 | 69 | ||
| 90 | const char* Logger::GetLevelName(Level log_level) { | 70 | const char* GetLevelName(Level log_level) { |
| 91 | #define LVL(x) case Level::x: return #x | 71 | #define LVL(x) case Level::x: return #x |
| 92 | switch (log_level) { | 72 | switch (log_level) { |
| 93 | LVL(Trace); | 73 | LVL(Trace); |
| @@ -101,19 +81,6 @@ const char* Logger::GetLevelName(Level log_level) { | |||
| 101 | #undef LVL | 81 | #undef LVL |
| 102 | } | 82 | } |
| 103 | 83 | ||
| 104 | void Logger::LogMessage(Entry entry) { | ||
| 105 | ring_buffer.Push(std::move(entry)); | ||
| 106 | } | ||
| 107 | |||
| 108 | size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) { | ||
| 109 | return ring_buffer.BlockingPop(out_buffer, buffer_len); | ||
| 110 | } | ||
| 111 | |||
| 112 | std::shared_ptr<Logger> InitGlobalLogger() { | ||
| 113 | global_logger = std::make_shared<Logger>(); | ||
| 114 | return global_logger; | ||
| 115 | } | ||
| 116 | |||
| 117 | Entry CreateEntry(Class log_class, Level log_level, | 84 | Entry CreateEntry(Class log_class, Level log_level, |
| 118 | const char* filename, unsigned int line_nr, const char* function, | 85 | const char* filename, unsigned int line_nr, const char* function, |
| 119 | const char* format, va_list args) { | 86 | const char* format, va_list args) { |
| @@ -138,7 +105,7 @@ Entry CreateEntry(Class log_class, Level log_level, | |||
| 138 | return std::move(entry); | 105 | return std::move(entry); |
| 139 | } | 106 | } |
| 140 | 107 | ||
| 141 | static Filter* filter; | 108 | static Filter* filter = nullptr; |
| 142 | 109 | ||
| 143 | void SetFilter(Filter* new_filter) { | 110 | void SetFilter(Filter* new_filter) { |
| 144 | filter = new_filter; | 111 | filter = new_filter; |
| @@ -147,7 +114,7 @@ void SetFilter(Filter* new_filter) { | |||
| 147 | void LogMessage(Class log_class, Level log_level, | 114 | void LogMessage(Class log_class, Level log_level, |
| 148 | const char* filename, unsigned int line_nr, const char* function, | 115 | const char* filename, unsigned int line_nr, const char* function, |
| 149 | const char* format, ...) { | 116 | const char* format, ...) { |
| 150 | if (!filter->CheckMessage(log_class, log_level)) | 117 | if (filter != nullptr && !filter->CheckMessage(log_class, log_level)) |
| 151 | return; | 118 | return; |
| 152 | 119 | ||
| 153 | va_list args; | 120 | va_list args; |
| @@ -156,12 +123,7 @@ void LogMessage(Class log_class, Level log_level, | |||
| 156 | filename, line_nr, function, format, args); | 123 | filename, line_nr, function, format, args); |
| 157 | va_end(args); | 124 | va_end(args); |
| 158 | 125 | ||
| 159 | if (global_logger != nullptr && !global_logger->IsClosed()) { | 126 | PrintColoredMessage(entry); |
| 160 | global_logger->LogMessage(std::move(entry)); | ||
| 161 | } else { | ||
| 162 | // Fall back to directly printing to stderr | ||
| 163 | PrintMessage(entry); | ||
| 164 | } | ||
| 165 | } | 127 | } |
| 166 | 128 | ||
| 167 | } | 129 | } |
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 3114f864c..c1f4d08e4 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h | |||
| @@ -4,17 +4,17 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <chrono> | ||
| 7 | #include <cstdarg> | 8 | #include <cstdarg> |
| 8 | #include <memory> | 9 | #include <string> |
| 9 | #include <vector> | 10 | #include <utility> |
| 10 | 11 | ||
| 11 | #include "common/concurrent_ring_buffer.h" | ||
| 12 | |||
| 13 | #include "common/logging/filter.h" | ||
| 14 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 15 | 13 | ||
| 16 | namespace Log { | 14 | namespace Log { |
| 17 | 15 | ||
| 16 | class Filter; | ||
| 17 | |||
| 18 | /** | 18 | /** |
| 19 | * A log entry. Log entries are store in a structured format to permit more varied output | 19 | * A log entry. Log entries are store in a structured format to permit more varied output |
| 20 | * formatting on different frontends, as well as facilitating filtering and aggregation. | 20 | * formatting on different frontends, as well as facilitating filtering and aggregation. |
| @@ -48,89 +48,21 @@ struct Entry { | |||
| 48 | } | 48 | } |
| 49 | }; | 49 | }; |
| 50 | 50 | ||
| 51 | struct ClassInfo { | ||
| 52 | Class log_class; | ||
| 53 | |||
| 54 | /** | ||
| 55 | * Total number of (direct or indirect) sub classes this class has. If any, they follow in | ||
| 56 | * sequence after this class in the class list. | ||
| 57 | */ | ||
| 58 | unsigned int num_children = 0; | ||
| 59 | |||
| 60 | ClassInfo(Class log_class) : log_class(log_class) {} | ||
| 61 | }; | ||
| 62 | |||
| 63 | /** | 51 | /** |
| 64 | * Logging management class. This class has the dual purpose of acting as an exchange point between | 52 | * Returns the name of the passed log class as a C-string. Subclasses are separated by periods |
| 65 | * the logging clients and the log outputter, as well as containing reflection info about available | 53 | * instead of underscores as in the enumeration. |
| 66 | * log classes. | ||
| 67 | */ | 54 | */ |
| 68 | class Logger { | 55 | const char* GetLogClassName(Class log_class); |
| 69 | private: | ||
| 70 | using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>; | ||
| 71 | |||
| 72 | public: | ||
| 73 | static const size_t QUEUE_CLOSED = Buffer::QUEUE_CLOSED; | ||
| 74 | |||
| 75 | Logger(); | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Returns a list of all vector classes and subclasses. The sequence returned is a pre-order of | ||
| 79 | * classes and subclasses, which together with the `num_children` field in ClassInfo, allows | ||
| 80 | * you to recover the hierarchy. | ||
| 81 | */ | ||
| 82 | const std::vector<ClassInfo>& GetClasses() const { return all_classes; } | ||
| 83 | 56 | ||
| 84 | /** | 57 | /** |
| 85 | * Returns the name of the passed log class as a C-string. Subclasses are separated by periods | 58 | * Returns the name of the passed log level as a C-string. |
| 86 | * instead of underscores as in the enumeration. | 59 | */ |
| 87 | */ | 60 | const char* GetLevelName(Level log_level); |
| 88 | static const char* GetLogClassName(Class log_class); | ||
| 89 | |||
| 90 | /** | ||
| 91 | * Returns the name of the passed log level as a C-string. | ||
| 92 | */ | ||
| 93 | static const char* GetLevelName(Level log_level); | ||
| 94 | |||
| 95 | /** | ||
| 96 | * Appends a messages to the log buffer. | ||
| 97 | * @note This function is thread safe. | ||
| 98 | */ | ||
| 99 | void LogMessage(Entry entry); | ||
| 100 | |||
| 101 | /** | ||
| 102 | * Retrieves a batch of messages from the log buffer, blocking until they are available. | ||
| 103 | * @note This function is thread safe. | ||
| 104 | * | ||
| 105 | * @param out_buffer Destination buffer that will receive the log entries. | ||
| 106 | * @param buffer_len The maximum size of `out_buffer`. | ||
| 107 | * @return The number of entries stored. In case the logger is shutting down, `QUEUE_CLOSED` is | ||
| 108 | * returned, no entries are stored and the logger should shutdown. | ||
| 109 | */ | ||
| 110 | size_t GetEntries(Entry* out_buffer, size_t buffer_len); | ||
| 111 | |||
| 112 | /** | ||
| 113 | * Initiates a shutdown of the logger. This will indicate to log output clients that they | ||
| 114 | * should shutdown. | ||
| 115 | */ | ||
| 116 | void Close() { ring_buffer.Close(); } | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Returns true if Close() has already been called on the Logger. | ||
| 120 | */ | ||
| 121 | bool IsClosed() const { return ring_buffer.IsClosed(); } | ||
| 122 | |||
| 123 | private: | ||
| 124 | Buffer ring_buffer; | ||
| 125 | std::vector<ClassInfo> all_classes; | ||
| 126 | }; | ||
| 127 | 61 | ||
| 128 | /// Creates a log entry by formatting the given source location, and message. | 62 | /// Creates a log entry by formatting the given source location, and message. |
| 129 | Entry CreateEntry(Class log_class, Level log_level, | 63 | Entry CreateEntry(Class log_class, Level log_level, |
| 130 | const char* filename, unsigned int line_nr, const char* function, | 64 | const char* filename, unsigned int line_nr, const char* function, |
| 131 | const char* format, va_list args); | 65 | const char* format, va_list args); |
| 132 | /// Initializes the default Logger. | ||
| 133 | std::shared_ptr<Logger> InitGlobalLogger(); | ||
| 134 | 66 | ||
| 135 | void SetFilter(Filter* filter); | 67 | void SetFilter(Filter* filter); |
| 136 | 68 | ||
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 50f2e13f4..55cc8888a 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp | |||
| @@ -22,16 +22,6 @@ void Filter::SetClassLevel(Class log_class, Level level) { | |||
| 22 | class_levels[static_cast<size_t>(log_class)] = level; | 22 | class_levels[static_cast<size_t>(log_class)] = level; |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) { | ||
| 26 | const size_t log_class_i = static_cast<size_t>(log_class.log_class); | ||
| 27 | |||
| 28 | const size_t begin = log_class_i + 1; | ||
| 29 | const size_t end = begin + log_class.num_children; | ||
| 30 | for (size_t i = begin; begin < end; ++i) { | ||
| 31 | class_levels[i] = level; | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | void Filter::ParseFilterString(const std::string& filter_str) { | 25 | void Filter::ParseFilterString(const std::string& filter_str) { |
| 36 | auto clause_begin = filter_str.cbegin(); | 26 | auto clause_begin = filter_str.cbegin(); |
| 37 | while (clause_begin != filter_str.cend()) { | 27 | while (clause_begin != filter_str.cend()) { |
| @@ -53,7 +43,7 @@ void Filter::ParseFilterString(const std::string& filter_str) { | |||
| 53 | template <typename It> | 43 | template <typename It> |
| 54 | static Level GetLevelByName(const It begin, const It end) { | 44 | static Level GetLevelByName(const It begin, const It end) { |
| 55 | for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) { | 45 | for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) { |
| 56 | const char* level_name = Logger::GetLevelName(static_cast<Level>(i)); | 46 | const char* level_name = GetLevelName(static_cast<Level>(i)); |
| 57 | if (Common::ComparePartialString(begin, end, level_name)) { | 47 | if (Common::ComparePartialString(begin, end, level_name)) { |
| 58 | return static_cast<Level>(i); | 48 | return static_cast<Level>(i); |
| 59 | } | 49 | } |
| @@ -64,7 +54,7 @@ static Level GetLevelByName(const It begin, const It end) { | |||
| 64 | template <typename It> | 54 | template <typename It> |
| 65 | static Class GetClassByName(const It begin, const It end) { | 55 | static Class GetClassByName(const It begin, const It end) { |
| 66 | for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) { | 56 | for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) { |
| 67 | const char* level_name = Logger::GetLogClassName(static_cast<Class>(i)); | 57 | const char* level_name = GetLogClassName(static_cast<Class>(i)); |
| 68 | if (Common::ComparePartialString(begin, end, level_name)) { | 58 | if (Common::ComparePartialString(begin, end, level_name)) { |
| 69 | return static_cast<Class>(i); | 59 | return static_cast<Class>(i); |
| 70 | } | 60 | } |
| @@ -72,20 +62,6 @@ static Class GetClassByName(const It begin, const It end) { | |||
| 72 | return Class::Count; | 62 | return Class::Count; |
| 73 | } | 63 | } |
| 74 | 64 | ||
| 75 | template <typename InputIt, typename T> | ||
| 76 | static InputIt find_last(InputIt begin, const InputIt end, const T& value) { | ||
| 77 | auto match = end; | ||
| 78 | while (begin != end) { | ||
| 79 | auto new_match = std::find(begin, end, value); | ||
| 80 | if (new_match != end) { | ||
| 81 | match = new_match; | ||
| 82 | ++new_match; | ||
| 83 | } | ||
| 84 | begin = new_match; | ||
| 85 | } | ||
| 86 | return match; | ||
| 87 | } | ||
| 88 | |||
| 89 | bool Filter::ParseFilterRule(const std::string::const_iterator begin, | 65 | bool Filter::ParseFilterRule(const std::string::const_iterator begin, |
| 90 | const std::string::const_iterator end) { | 66 | const std::string::const_iterator end) { |
| 91 | auto level_separator = std::find(begin, end, ':'); | 67 | auto level_separator = std::find(begin, end, ':'); |
| @@ -106,22 +82,13 @@ bool Filter::ParseFilterRule(const std::string::const_iterator begin, | |||
| 106 | return true; | 82 | return true; |
| 107 | } | 83 | } |
| 108 | 84 | ||
| 109 | auto class_name_end = find_last(begin, level_separator, '.'); | 85 | const Class log_class = GetClassByName(begin, level_separator); |
| 110 | if (class_name_end != level_separator && | ||
| 111 | !Common::ComparePartialString(class_name_end + 1, level_separator, "*")) { | ||
| 112 | class_name_end = level_separator; | ||
| 113 | } | ||
| 114 | |||
| 115 | const Class log_class = GetClassByName(begin, class_name_end); | ||
| 116 | if (log_class == Class::Count) { | 86 | if (log_class == Class::Count) { |
| 117 | LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str()); | 87 | LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str()); |
| 118 | return false; | 88 | return false; |
| 119 | } | 89 | } |
| 120 | 90 | ||
| 121 | if (class_name_end == level_separator) { | 91 | SetClassLevel(log_class, level); |
| 122 | SetClassLevel(log_class, level); | ||
| 123 | } | ||
| 124 | SetSubclassesLevel(log_class, level); | ||
| 125 | return true; | 92 | return true; |
| 126 | } | 93 | } |
| 127 | 94 | ||
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index b53e4e633..0b71ea3b2 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h | |||
| @@ -11,8 +11,6 @@ | |||
| 11 | 11 | ||
| 12 | namespace Log { | 12 | namespace Log { |
| 13 | 13 | ||
| 14 | struct ClassInfo; | ||
| 15 | |||
| 16 | /** | 14 | /** |
| 17 | * Implements a log message filter which allows different log classes to have different minimum | 15 | * Implements a log message filter which allows different log classes to have different minimum |
| 18 | * severity levels. The filter can be changed at runtime and can be parsed from a string to allow | 16 | * severity levels. The filter can be changed at runtime and can be parsed from a string to allow |
| @@ -27,29 +25,19 @@ public: | |||
| 27 | void ResetAll(Level level); | 25 | void ResetAll(Level level); |
| 28 | /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. | 26 | /// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. |
| 29 | void SetClassLevel(Class log_class, Level level); | 27 | void SetClassLevel(Class log_class, Level level); |
| 30 | /** | ||
| 31 | * Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class` | ||
| 32 | * itself is not changed. | ||
| 33 | */ | ||
| 34 | void SetSubclassesLevel(const ClassInfo& log_class, Level level); | ||
| 35 | 28 | ||
| 36 | /** | 29 | /** |
| 37 | * Parses a filter string and applies it to this filter. | 30 | * Parses a filter string and applies it to this filter. |
| 38 | * | 31 | * |
| 39 | * A filter string consists of a space-separated list of filter rules, each of the format | 32 | * A filter string consists of a space-separated list of filter rules, each of the format |
| 40 | * `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods. | 33 | * `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods. |
| 41 | * A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and | 34 | * `*` is allowed as a class name and will reset all filters to the specified level. `<level>` |
| 42 | * can be used to apply a rule to all classes or to all subclasses of a class without affecting | 35 | * a severity level name which will be set as the minimum logging level of the matched classes. |
| 43 | * the parent class. `<level>` a severity level name which will be set as the minimum logging | 36 | * Rules are applied left to right, with each rule overriding previous ones in the sequence. |
| 44 | * level of the matched classes. Rules are applied left to right, with each rule overriding | ||
| 45 | * previous ones in the sequence. | ||
| 46 | * | 37 | * |
| 47 | * A few examples of filter rules: | 38 | * A few examples of filter rules: |
| 48 | * - `*:Info` -- Resets the level of all classes to Info. | 39 | * - `*:Info` -- Resets the level of all classes to Info. |
| 49 | * - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT, | 40 | * - `Service:Info` -- Sets the level of Service to Info. |
| 50 | * etc.) to Info. | ||
| 51 | * - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the | ||
| 52 | * level of Service unchanged. | ||
| 53 | * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. | 41 | * - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. |
| 54 | */ | 42 | */ |
| 55 | void ParseFilterString(const std::string& filter_str); | 43 | void ParseFilterString(const std::string& filter_str); |
diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 123641cb4..fd87ddbe6 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h | |||
| @@ -78,11 +78,7 @@ enum class Class : ClassType { | |||
| 78 | Count ///< Total number of logging classes | 78 | Count ///< Total number of logging classes |
| 79 | }; | 79 | }; |
| 80 | 80 | ||
| 81 | /** | 81 | /// Logs a message to the global logger. |
| 82 | * Logs a message to the global logger. This proxy exists to avoid exposing the details of the | ||
| 83 | * Logger class, including the ConcurrentRingBuffer template, to all files that desire to log | ||
| 84 | * messages, reducing unecessary recompilations. | ||
| 85 | */ | ||
| 86 | void LogMessage(Class log_class, Level log_level, | 82 | void LogMessage(Class log_class, Level log_level, |
| 87 | const char* filename, unsigned int line_nr, const char* function, | 83 | const char* filename, unsigned int line_nr, const char* function, |
| 88 | #ifdef _MSC_VER | 84 | #ifdef _MSC_VER |
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 45be6d0a1..94f3dfc1f 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp | |||
| @@ -46,8 +46,8 @@ void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) { | |||
| 46 | unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000); | 46 | unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000); |
| 47 | unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); | 47 | unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); |
| 48 | 48 | ||
| 49 | const char* class_name = Logger::GetLogClassName(entry.log_class); | 49 | const char* class_name = GetLogClassName(entry.log_class); |
| 50 | const char* level_name = Logger::GetLevelName(entry.log_level); | 50 | const char* level_name = GetLevelName(entry.log_level); |
| 51 | 51 | ||
| 52 | snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", | 52 | snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", |
| 53 | time_seconds, time_fractional, class_name, level_name, | 53 | time_seconds, time_fractional, class_name, level_name, |
| @@ -116,19 +116,4 @@ void PrintColoredMessage(const Entry& entry) { | |||
| 116 | #endif | 116 | #endif |
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | void TextLoggingLoop(std::shared_ptr<Logger> logger) { | ||
| 120 | std::array<Entry, 256> entry_buffer; | ||
| 121 | |||
| 122 | while (true) { | ||
| 123 | size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size()); | ||
| 124 | if (num_entries == Logger::QUEUE_CLOSED) { | ||
| 125 | break; | ||
| 126 | } | ||
| 127 | for (size_t i = 0; i < num_entries; ++i) { | ||
| 128 | const Entry& entry = entry_buffer[i]; | ||
| 129 | PrintColoredMessage(entry); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | } | 119 | } |
diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 8474a1904..5b82f043f 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h | |||
| @@ -5,11 +5,9 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <cstddef> | 7 | #include <cstddef> |
| 8 | #include <memory> | ||
| 9 | 8 | ||
| 10 | namespace Log { | 9 | namespace Log { |
| 11 | 10 | ||
| 12 | class Logger; | ||
| 13 | struct Entry; | 11 | struct Entry; |
| 14 | 12 | ||
| 15 | /** | 13 | /** |
| @@ -31,10 +29,4 @@ void PrintMessage(const Entry& entry); | |||
| 31 | /// Prints the same message as `PrintMessage`, but colored acoording to the severity level. | 29 | /// Prints the same message as `PrintMessage`, but colored acoording to the severity level. |
| 32 | void PrintColoredMessage(const Entry& entry); | 30 | void PrintColoredMessage(const Entry& entry); |
| 33 | 31 | ||
| 34 | /** | ||
| 35 | * Logging loop that repeatedly reads messages from the provided logger and prints them to the | ||
| 36 | * console. It is the baseline barebones log outputter. | ||
| 37 | */ | ||
| 38 | void TextLoggingLoop(std::shared_ptr<Logger> logger); | ||
| 39 | |||
| 40 | } | 32 | } |