summaryrefslogtreecommitdiff
path: root/src/common/logging/backend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/logging/backend.cpp')
-rw-r--r--src/common/logging/backend.cpp355
1 files changed, 216 insertions, 139 deletions
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 61dddab3f..0e85a9c1d 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -2,118 +2,244 @@
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> 8#include <exception>
10#include <memory>
11#include <mutex>
12#include <thread> 9#include <thread>
13#include <vector> 10#include <vector>
14 11
12#include <fmt/format.h>
13
15#ifdef _WIN32 14#ifdef _WIN32
16#include <windows.h> // For OutputDebugStringW 15#include <windows.h> // For OutputDebugStringW
17#endif 16#endif
18 17
19#include "common/assert.h"
20#include "common/fs/file.h" 18#include "common/fs/file.h"
21#include "common/fs/fs.h" 19#include "common/fs/fs.h"
20#include "common/fs/fs_paths.h"
21#include "common/fs/path_util.h"
22#include "common/literals.h" 22#include "common/literals.h"
23#include "common/thread.h"
23 24
24#include "common/logging/backend.h" 25#include "common/logging/backend.h"
25#include "common/logging/log.h" 26#include "common/logging/log.h"
27#include "common/logging/log_entry.h"
26#include "common/logging/text_formatter.h" 28#include "common/logging/text_formatter.h"
27#include "common/settings.h" 29#include "common/settings.h"
30#ifdef _WIN32
28#include "common/string_util.h" 31#include "common/string_util.h"
32#endif
29#include "common/threadsafe_queue.h" 33#include "common/threadsafe_queue.h"
30 34
31namespace Common::Log { 35namespace Common::Log {
32 36
37namespace {
38
33/** 39/**
34 * Static state as a singleton. 40 * Interface for logging backends.
35 */ 41 */
36class Impl { 42class Backend {
37public: 43public:
38 static Impl& Instance() { 44 virtual ~Backend() = default;
39 static Impl backend; 45
40 return backend; 46 virtual void Write(const Entry& entry) = 0;
47
48 virtual void EnableForStacktrace() = 0;
49
50 virtual void Flush() = 0;
51};
52
53/**
54 * Backend that writes to stderr and with color
55 */
56class ColorConsoleBackend final : public Backend {
57public:
58 explicit ColorConsoleBackend() = default;
59
60 ~ColorConsoleBackend() override = default;
61
62 void Write(const Entry& entry) override {
63 if (enabled.load(std::memory_order_relaxed)) {
64 PrintColoredMessage(entry);
65 }
41 } 66 }
42 67
43 Impl(const Impl&) = delete; 68 void Flush() override {
44 Impl& operator=(const Impl&) = delete; 69 // stderr shouldn't be buffered
70 }
45 71
46 Impl(Impl&&) = delete; 72 void EnableForStacktrace() override {
47 Impl& operator=(Impl&&) = delete; 73 enabled = true;
74 }
48 75
49 void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, 76 void SetEnabled(bool enabled_) {
50 const char* function, std::string message) { 77 enabled = enabled_;
51 message_queue.Push(
52 CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
53 } 78 }
54 79
55 void AddBackend(std::unique_ptr<Backend> backend) { 80private:
56 std::lock_guard lock{writing_mutex}; 81 std::atomic_bool enabled{false};
57 backends.push_back(std::move(backend)); 82};
83
84/**
85 * Backend that writes to a file passed into the constructor
86 */
87class FileBackend final : public Backend {
88public:
89 explicit FileBackend(const std::filesystem::path& filename) {
90 auto old_filename = filename;
91 old_filename += ".old.txt";
92
93 // Existence checks are done within the functions themselves.
94 // We don't particularly care if these succeed or not.
95 static_cast<void>(FS::RemoveFile(old_filename));
96 static_cast<void>(FS::RenameFile(filename, old_filename));
97
98 file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write,
99 FS::FileType::TextFile);
100 }
101
102 ~FileBackend() override = default;
103
104 void Write(const Entry& entry) override {
105 if (!enabled) {
106 return;
107 }
108
109 bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
110
111 using namespace Common::Literals;
112 // Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
113 const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
114 const bool write_limit_exceeded = bytes_written > write_limit;
115 if (entry.log_level >= Level::Error || write_limit_exceeded) {
116 if (write_limit_exceeded) {
117 // Stop writing after the write limit is exceeded.
118 // Don't close the file so we can print a stacktrace if necessary
119 enabled = false;
120 }
121 file->Flush();
122 }
58 } 123 }
59 124
60 void RemoveBackend(std::string_view backend_name) { 125 void Flush() override {
61 std::lock_guard lock{writing_mutex}; 126 file->Flush();
127 }
62 128
63 std::erase_if(backends, [&backend_name](const auto& backend) { 129 void EnableForStacktrace() override {
64 return backend_name == backend->GetName(); 130 enabled = true;
65 }); 131 bytes_written = 0;
66 } 132 }
67 133
68 const Filter& GetGlobalFilter() const { 134private:
69 return filter; 135 std::unique_ptr<FS::IOFile> file;
136 bool enabled = true;
137 std::size_t bytes_written = 0;
138};
139
140/**
141 * Backend that writes to Visual Studio's output window
142 */
143class DebuggerBackend final : public Backend {
144public:
145 explicit DebuggerBackend() = default;
146
147 ~DebuggerBackend() override = default;
148
149 void Write(const Entry& entry) override {
150#ifdef _WIN32
151 ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
152#endif
70 } 153 }
71 154
155 void Flush() override {}
156
157 void EnableForStacktrace() override {}
158};
159
160bool initialization_in_progress_suppress_logging = true;
161
162/**
163 * Static state as a singleton.
164 */
165class Impl {
166public:
167 static Impl& Instance() {
168 if (!instance) {
169 throw std::runtime_error("Using Logging instance before its initialization");
170 }
171 return *instance;
172 }
173
174 static void Initialize() {
175 if (instance) {
176 LOG_WARNING(Log, "Reinitializing logging backend");
177 return;
178 }
179 using namespace Common::FS;
180 const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
181 void(CreateDir(log_dir));
182 Filter filter;
183 filter.ParseFilterString(Settings::values.log_filter.GetValue());
184 instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
185 Deleter);
186 initialization_in_progress_suppress_logging = false;
187 }
188
189 Impl(const Impl&) = delete;
190 Impl& operator=(const Impl&) = delete;
191
192 Impl(Impl&&) = delete;
193 Impl& operator=(Impl&&) = delete;
194
72 void SetGlobalFilter(const Filter& f) { 195 void SetGlobalFilter(const Filter& f) {
73 filter = f; 196 filter = f;
74 } 197 }
75 198
76 Backend* GetBackend(std::string_view backend_name) { 199 void SetColorConsoleBackendEnabled(bool enabled) {
77 const auto it = 200 color_console_backend.SetEnabled(enabled);
78 std::find_if(backends.begin(), backends.end(), 201 }
79 [&backend_name](const auto& i) { return backend_name == i->GetName(); }); 202
80 if (it == backends.end()) 203 void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
81 return nullptr; 204 const char* function, std::string message) {
82 return it->get(); 205 if (!filter.CheckMessage(log_class, log_level))
206 return;
207 const Entry& entry =
208 CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
209 message_queue.Push(entry);
83 } 210 }
84 211
85private: 212private:
86 Impl() { 213 Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
87 backend_thread = std::thread([&] { 214 : filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] {
88 Entry entry; 215 Common::SetCurrentThreadName("yuzu:Log");
89 auto write_logs = [&](Entry& e) { 216 Entry entry;
90 std::lock_guard lock{writing_mutex}; 217 const auto write_logs = [this, &entry]() {
91 for (const auto& backend : backends) { 218 ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
92 backend->Write(e); 219 };
93 } 220 while (true) {
94 }; 221 entry = message_queue.PopWait();
95 while (true) { 222 if (entry.final_entry) {
96 entry = message_queue.PopWait(); 223 break;
97 if (entry.final_entry) { 224 }
98 break; 225 write_logs();
99 } 226 }
100 write_logs(entry); 227 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
101 } 228 // case where a system is repeatedly spamming logs even on close.
229 int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
230 while (max_logs_to_write-- && message_queue.Pop(entry)) {
231 write_logs();
232 }
233 })} {}
102 234
103 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a 235 ~Impl() {
104 // case where a system is repeatedly spamming logs even on close. 236 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 } 237 }
112 238
113 ~Impl() { 239 void StopBackendThread() {
114 Entry entry; 240 Entry stop_entry{};
115 entry.final_entry = true; 241 stop_entry.final_entry = true;
116 message_queue.Push(entry); 242 message_queue.Push(stop_entry);
117 backend_thread.join(); 243 backend_thread.join();
118 } 244 }
119 245
@@ -135,100 +261,51 @@ private:
135 }; 261 };
136 } 262 }
137 263
138 std::mutex writing_mutex; 264 void ForEachBackend(auto lambda) {
139 std::thread backend_thread; 265 lambda(static_cast<Backend&>(debugger_backend));
140 std::vector<std::unique_ptr<Backend>> backends; 266 lambda(static_cast<Backend&>(color_console_backend));
141 MPSCQueue<Entry> message_queue; 267 lambda(static_cast<Backend&>(file_backend));
142 Filter filter; 268 }
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 269
173void FileBackend::Write(const Entry& entry) { 270 static void Deleter(Impl* ptr) {
174 if (!file->IsOpen()) { 271 delete ptr;
175 return;
176 } 272 }
177 273
178 using namespace Common::Literals; 274 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 275
183 const bool write_limit_exceeded = 276 Filter filter;
184 bytes_written > MAX_BYTES_WRITTEN_EXTENDED || 277 DebuggerBackend debugger_backend{};
185 (bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging); 278 ColorConsoleBackend color_console_backend{};
279 FileBackend file_backend;
186 280
187 // Close the file after the write limit is exceeded. 281 std::thread backend_thread;
188 if (write_limit_exceeded) { 282 MPSCQueue<Entry> message_queue{};
189 file->Close(); 283 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
190 return; 284};
191 } 285} // namespace
192 286
193 bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); 287void Initialize() {
194 if (entry.log_level >= Level::Error) { 288 Impl::Initialize();
195 file->Flush();
196 }
197} 289}
198 290
199DebuggerBackend::~DebuggerBackend() = default; 291void DisableLoggingInTests() {
200 292 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} 293}
206 294
207void SetGlobalFilter(const Filter& filter) { 295void SetGlobalFilter(const Filter& filter) {
208 Impl::Instance().SetGlobalFilter(filter); 296 Impl::Instance().SetGlobalFilter(filter);
209} 297}
210 298
211void AddBackend(std::unique_ptr<Backend> backend) { 299void SetColorConsoleBackendEnabled(bool enabled) {
212 Impl::Instance().AddBackend(std::move(backend)); 300 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} 301}
222 302
223void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, 303void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
224 unsigned int line_num, const char* function, const char* format, 304 unsigned int line_num, const char* function, const char* format,
225 const fmt::format_args& args) { 305 const fmt::format_args& args) {
226 auto& instance = Impl::Instance(); 306 if (!initialization_in_progress_suppress_logging) {
227 const auto& filter = instance.GetGlobalFilter(); 307 Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
228 if (!filter.CheckMessage(log_class, log_level)) 308 fmt::vformat(format, args));
229 return; 309 }
230
231 instance.PushEntry(log_class, log_level, filename, line_num, function,
232 fmt::vformat(format, args));
233} 310}
234} // namespace Common::Log 311} // namespace Common::Log