summaryrefslogtreecommitdiff
path: root/src/common/logging/backend.cpp
diff options
context:
space:
mode:
authorGravatar yzct123452021-08-13 18:39:45 +0000
committerGravatar GitHub2021-08-13 18:39:45 +0000
commit001675dced1b7b751d1db4f0d6490776c613df2f (patch)
tree151923c90c42bdcede2581ddf6b56768bfcafc01 /src/common/logging/backend.cpp
parentMerge pull request #6862 from german77/badsdl (diff)
downloadyuzu-001675dced1b7b751d1db4f0d6490776c613df2f.tar.gz
yuzu-001675dced1b7b751d1db4f0d6490776c613df2f.tar.xz
yuzu-001675dced1b7b751d1db4f0d6490776c613df2f.zip
logging: Simplify and make thread-safe
This simplifies the logging system. This also fixes some lost messages on startup. The simplification is simple. I removed unused functions and moved most things in the .h to the .cpp. I replaced the unnecessary linked list with its contents laid out as three member variables. Anything that went through the linked list now directly accesses the backends. Generic functions are replaced with those for each specific use case and there aren't many. This change increases coupling but we gain back more KISS and encapsulation. With those changes it was easy to make it thread-safe. I just removed the mutex and turned a boolean atomic. I was planning to use this thread-safety in my next PR about stacktraces. It was actually async-signal-safety at first but I ended up using a different approach. Anyway getting rid of the linked list is important for that because have the list of backends constantly changing complicates things.
Diffstat (limited to 'src/common/logging/backend.cpp')
-rw-r--r--src/common/logging/backend.cpp350
1 files changed, 211 insertions, 139 deletions
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 61dddab3f..13edda9c9 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -2,13 +2,9 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
6#include <atomic> 5#include <atomic>
7#include <chrono> 6#include <chrono>
8#include <climits> 7#include <climits>
9#include <condition_variable>
10#include <memory>
11#include <mutex>
12#include <thread> 8#include <thread>
13#include <vector> 9#include <vector>
14 10
@@ -16,104 +12,229 @@
16#include <windows.h> // For OutputDebugStringW 12#include <windows.h> // For OutputDebugStringW
17#endif 13#endif
18 14
19#include "common/assert.h"
20#include "common/fs/file.h" 15#include "common/fs/file.h"
21#include "common/fs/fs.h" 16#include "common/fs/fs.h"
17#include "common/fs/fs_paths.h"
18#include "common/fs/path_util.h"
22#include "common/literals.h" 19#include "common/literals.h"
23 20
24#include "common/logging/backend.h" 21#include "common/logging/backend.h"
25#include "common/logging/log.h" 22#include "common/logging/log.h"
26#include "common/logging/text_formatter.h" 23#include "common/logging/text_formatter.h"
27#include "common/settings.h" 24#include "common/settings.h"
25#ifdef _WIN32
28#include "common/string_util.h" 26#include "common/string_util.h"
27#endif
29#include "common/threadsafe_queue.h" 28#include "common/threadsafe_queue.h"
30 29
31namespace Common::Log { 30namespace Common::Log {
32 31
32namespace {
33
33/** 34/**
34 * Static state as a singleton. 35 * Interface for logging backends.
35 */ 36 */
36class Impl { 37class Backend {
37public: 38public:
38 static Impl& Instance() { 39 virtual ~Backend() = default;
39 static Impl backend; 40
40 return backend; 41 virtual void Write(const Entry& entry) = 0;
42
43 virtual void EnableForStacktrace() = 0;
44
45 virtual void Flush() = 0;
46};
47
48/**
49 * Backend that writes to stderr and with color
50 */
51class ColorConsoleBackend final : public Backend {
52public:
53 explicit ColorConsoleBackend() = default;
54
55 ~ColorConsoleBackend() override = default;
56
57 void Write(const Entry& entry) override {
58 if (enabled.load(std::memory_order_relaxed)) {
59 PrintColoredMessage(entry);
60 }
41 } 61 }
42 62
43 Impl(const Impl&) = delete; 63 void Flush() override {
44 Impl& operator=(const Impl&) = delete; 64 // stderr shouldn't be buffered
65 }
45 66
46 Impl(Impl&&) = delete; 67 void EnableForStacktrace() override {
47 Impl& operator=(Impl&&) = delete; 68 enabled = true;
69 }
48 70
49 void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, 71 void SetEnabled(bool enabled_) {
50 const char* function, std::string message) { 72 enabled = enabled_;
51 message_queue.Push( 73 }
52 CreateEntry(log_class, log_level, filename, line_num, function, std::move(message))); 74
75private:
76 std::atomic_bool enabled{false};
77};
78
79/**
80 * Backend that writes to a file passed into the constructor
81 */
82class FileBackend final : public Backend {
83public:
84 explicit FileBackend(const std::filesystem::path& filename) {
85 auto old_filename = filename;
86 old_filename += ".old.txt";
87
88 // Existence checks are done within the functions themselves.
89 // We don't particularly care if these succeed or not.
90 static_cast<void>(FS::RemoveFile(old_filename));
91 static_cast<void>(FS::RenameFile(filename, old_filename));
92
93 file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write,
94 FS::FileType::TextFile);
95 }
96
97 ~FileBackend() override = default;
98
99 void Write(const Entry& entry) override {
100 if (!enabled) {
101 return;
102 }
103
104 bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
105
106 using namespace Common::Literals;
107 // Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
108 const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
109 const bool write_limit_exceeded = bytes_written > write_limit;
110 if (entry.log_level >= Level::Error || write_limit_exceeded) {
111 if (write_limit_exceeded) {
112 // Stop writing after the write limit is exceeded.
113 // Don't close the file so we can print a stacktrace if necessary
114 enabled = false;
115 }
116 file->Flush();
117 }
118 }
119
120 void Flush() override {
121 file->Flush();
122 }
123
124 void EnableForStacktrace() override {
125 enabled = true;
126 bytes_written = 0;
53 } 127 }
54 128
55 void AddBackend(std::unique_ptr<Backend> backend) { 129private:
56 std::lock_guard lock{writing_mutex}; 130 std::unique_ptr<FS::IOFile> file;
57 backends.push_back(std::move(backend)); 131 bool enabled = true;
132 std::size_t bytes_written = 0;
133};
134
135/**
136 * Backend that writes to Visual Studio's output window
137 */
138class DebuggerBackend final : public Backend {
139public:
140 explicit DebuggerBackend() = default;
141
142 ~DebuggerBackend() override = default;
143
144 void Write(const Entry& entry) override {
145#ifdef _WIN32
146 ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
147#endif
58 } 148 }
59 149
60 void RemoveBackend(std::string_view backend_name) { 150 void Flush() override {}
61 std::lock_guard lock{writing_mutex}; 151
152 void EnableForStacktrace() override {}
153};
154
155bool initialization_in_progress_suppress_logging = false;
62 156
63 std::erase_if(backends, [&backend_name](const auto& backend) { 157/**
64 return backend_name == backend->GetName(); 158 * Static state as a singleton.
65 }); 159 */
160class Impl {
161public:
162 static Impl& Instance() {
163 if (!instance) {
164 abort();
165 }
166 return *instance;
66 } 167 }
67 168
68 const Filter& GetGlobalFilter() const { 169 static void Initialize() {
69 return filter; 170 if (instance) {
171 abort();
172 }
173 using namespace Common::FS;
174 initialization_in_progress_suppress_logging = true;
175 const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
176 void(CreateDir(log_dir));
177 Filter filter;
178 filter.ParseFilterString(Settings::values.log_filter.GetValue());
179 instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
180 Deleter);
181 initialization_in_progress_suppress_logging = false;
70 } 182 }
71 183
184 Impl(const Impl&) = delete;
185 Impl& operator=(const Impl&) = delete;
186
187 Impl(Impl&&) = delete;
188 Impl& operator=(Impl&&) = delete;
189
72 void SetGlobalFilter(const Filter& f) { 190 void SetGlobalFilter(const Filter& f) {
73 filter = f; 191 filter = f;
74 } 192 }
75 193
76 Backend* GetBackend(std::string_view backend_name) { 194 void SetColorConsoleBackendEnabled(bool enabled) {
77 const auto it = 195 color_console_backend.SetEnabled(enabled);
78 std::find_if(backends.begin(), backends.end(), 196 }
79 [&backend_name](const auto& i) { return backend_name == i->GetName(); }); 197
80 if (it == backends.end()) 198 void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
81 return nullptr; 199 const char* function, std::string message) {
82 return it->get(); 200 if (!filter.CheckMessage(log_class, log_level))
201 return;
202 const Entry& entry =
203 CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
204 message_queue.Push(entry);
83 } 205 }
84 206
85private: 207private:
86 Impl() { 208 Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
87 backend_thread = std::thread([&] { 209 : filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] {
88 Entry entry; 210 Common::SetCurrentThreadName("yuzu:Log");
89 auto write_logs = [&](Entry& e) { 211 Entry entry;
90 std::lock_guard lock{writing_mutex}; 212 const auto write_logs = [this, &entry]() {
91 for (const auto& backend : backends) { 213 ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
92 backend->Write(e); 214 };
93 } 215 while (true) {
94 }; 216 entry = message_queue.PopWait();
95 while (true) { 217 if (entry.final_entry) {
96 entry = message_queue.PopWait(); 218 break;
97 if (entry.final_entry) { 219 }
98 break; 220 write_logs();
99 } 221 }
100 write_logs(entry); 222 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
101 } 223 // case where a system is repeatedly spamming logs even on close.
224 int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
225 while (max_logs_to_write-- && message_queue.Pop(entry)) {
226 write_logs();
227 }
228 })} {}
102 229
103 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a 230 ~Impl() {
104 // case where a system is repeatedly spamming logs even on close. 231 StopBackendThread();
105 const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
106 int logs_written = 0;
107 while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
108 write_logs(entry);
109 }
110 });
111 } 232 }
112 233
113 ~Impl() { 234 void StopBackendThread() {
114 Entry entry; 235 Entry stop_entry{};
115 entry.final_entry = true; 236 stop_entry.final_entry = true;
116 message_queue.Push(entry); 237 message_queue.Push(stop_entry);
117 backend_thread.join(); 238 backend_thread.join();
118 } 239 }
119 240
@@ -135,100 +256,51 @@ private:
135 }; 256 };
136 } 257 }
137 258
138 std::mutex writing_mutex; 259 void ForEachBackend(auto lambda) {
139 std::thread backend_thread; 260 lambda(static_cast<Backend&>(debugger_backend));
140 std::vector<std::unique_ptr<Backend>> backends; 261 lambda(static_cast<Backend&>(color_console_backend));
141 MPSCQueue<Entry> message_queue; 262 lambda(static_cast<Backend&>(file_backend));
142 Filter filter; 263 }
143 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
144};
145
146ConsoleBackend::~ConsoleBackend() = default;
147
148void ConsoleBackend::Write(const Entry& entry) {
149 PrintMessage(entry);
150}
151
152ColorConsoleBackend::~ColorConsoleBackend() = default;
153
154void ColorConsoleBackend::Write(const Entry& entry) {
155 PrintColoredMessage(entry);
156}
157
158FileBackend::FileBackend(const std::filesystem::path& filename) {
159 auto old_filename = filename;
160 old_filename += ".old.txt";
161
162 // Existence checks are done within the functions themselves.
163 // We don't particularly care if these succeed or not.
164 FS::RemoveFile(old_filename);
165 void(FS::RenameFile(filename, old_filename));
166
167 file =
168 std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
169}
170
171FileBackend::~FileBackend() = default;
172 264
173void FileBackend::Write(const Entry& entry) { 265 static void Deleter(Impl* ptr) {
174 if (!file->IsOpen()) { 266 delete ptr;
175 return;
176 } 267 }
177 268
178 using namespace Common::Literals; 269 static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
179 // Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
180 constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB;
181 constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB;
182 270
183 const bool write_limit_exceeded = 271 Filter filter;
184 bytes_written > MAX_BYTES_WRITTEN_EXTENDED || 272 DebuggerBackend debugger_backend{};
185 (bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging); 273 ColorConsoleBackend color_console_backend{};
274 FileBackend file_backend;
186 275
187 // Close the file after the write limit is exceeded. 276 std::thread backend_thread;
188 if (write_limit_exceeded) { 277 MPSCQueue<Entry> message_queue{};
189 file->Close(); 278 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
190 return; 279};
191 } 280} // namespace
192 281
193 bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); 282void Initialize() {
194 if (entry.log_level >= Level::Error) { 283 Impl::Initialize();
195 file->Flush();
196 }
197} 284}
198 285
199DebuggerBackend::~DebuggerBackend() = default; 286void DisableLoggingInTests() {
200 287 initialization_in_progress_suppress_logging = true;
201void DebuggerBackend::Write(const Entry& entry) {
202#ifdef _WIN32
203 ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
204#endif
205} 288}
206 289
207void SetGlobalFilter(const Filter& filter) { 290void SetGlobalFilter(const Filter& filter) {
208 Impl::Instance().SetGlobalFilter(filter); 291 Impl::Instance().SetGlobalFilter(filter);
209} 292}
210 293
211void AddBackend(std::unique_ptr<Backend> backend) { 294void SetColorConsoleBackendEnabled(bool enabled) {
212 Impl::Instance().AddBackend(std::move(backend)); 295 Impl::Instance().SetColorConsoleBackendEnabled(enabled);
213}
214
215void RemoveBackend(std::string_view backend_name) {
216 Impl::Instance().RemoveBackend(backend_name);
217}
218
219Backend* GetBackend(std::string_view backend_name) {
220 return Impl::Instance().GetBackend(backend_name);
221} 296}
222 297
223void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, 298void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
224 unsigned int line_num, const char* function, const char* format, 299 unsigned int line_num, const char* function, const char* format,
225 const fmt::format_args& args) { 300 const fmt::format_args& args) {
226 auto& instance = Impl::Instance(); 301 if (!initialization_in_progress_suppress_logging) {
227 const auto& filter = instance.GetGlobalFilter(); 302 Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
228 if (!filter.CheckMessage(log_class, log_level)) 303 fmt::vformat(format, args));
229 return; 304 }
230
231 instance.PushEntry(log_class, log_level, filename, line_num, function,
232 fmt::vformat(format, args));
233} 305}
234} // namespace Common::Log 306} // namespace Common::Log