summaryrefslogtreecommitdiff
path: root/src/common/logging
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/logging')
-rw-r--r--src/common/logging/backend.cpp355
-rw-r--r--src/common/logging/backend.h113
-rw-r--r--src/common/logging/filter.cpp1
-rw-r--r--src/common/logging/log.h6
-rw-r--r--src/common/logging/log_entry.h28
-rw-r--r--src/common/logging/text_formatter.cpp1
-rw-r--r--src/common/logging/types.h18
7 files changed, 259 insertions, 263 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
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index 4b9a910c1..cb7839ee9 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -5,120 +5,21 @@
5#pragma once 5#pragma once
6 6
7#include <filesystem> 7#include <filesystem>
8#include <memory>
9#include <string>
10#include <string_view>
11#include "common/logging/filter.h" 8#include "common/logging/filter.h"
12#include "common/logging/log.h"
13
14namespace Common::FS {
15class IOFile;
16}
17 9
18namespace Common::Log { 10namespace Common::Log {
19 11
20class Filter; 12class Filter;
21 13
22/** 14/// Initializes the logging system. This should be the first thing called in main.
23 * Interface for logging backends. As loggers can be created and removed at runtime, this can be 15void Initialize();
24 * used by a frontend for adding a custom logging backend as needed
25 */
26class Backend {
27public:
28 virtual ~Backend() = default;
29
30 virtual void SetFilter(const Filter& new_filter) {
31 filter = new_filter;
32 }
33 virtual const char* GetName() const = 0;
34 virtual void Write(const Entry& entry) = 0;
35
36private:
37 Filter filter;
38};
39
40/**
41 * Backend that writes to stderr without any color commands
42 */
43class ConsoleBackend : public Backend {
44public:
45 ~ConsoleBackend() override;
46
47 static const char* Name() {
48 return "console";
49 }
50 const char* GetName() const override {
51 return Name();
52 }
53 void Write(const Entry& entry) override;
54};
55
56/**
57 * Backend that writes to stderr and with color
58 */
59class ColorConsoleBackend : public Backend {
60public:
61 ~ColorConsoleBackend() override;
62
63 static const char* Name() {
64 return "color_console";
65 }
66
67 const char* GetName() const override {
68 return Name();
69 }
70 void Write(const Entry& entry) override;
71};
72 16
73/** 17void DisableLoggingInTests();
74 * Backend that writes to a file passed into the constructor
75 */
76class FileBackend : public Backend {
77public:
78 explicit FileBackend(const std::filesystem::path& filename);
79 ~FileBackend() override;
80
81 static const char* Name() {
82 return "file";
83 }
84
85 const char* GetName() const override {
86 return Name();
87 }
88
89 void Write(const Entry& entry) override;
90
91private:
92 std::unique_ptr<FS::IOFile> file;
93 std::size_t bytes_written = 0;
94};
95
96/**
97 * Backend that writes to Visual Studio's output window
98 */
99class DebuggerBackend : public Backend {
100public:
101 ~DebuggerBackend() override;
102
103 static const char* Name() {
104 return "debugger";
105 }
106 const char* GetName() const override {
107 return Name();
108 }
109 void Write(const Entry& entry) override;
110};
111
112void AddBackend(std::unique_ptr<Backend> backend);
113
114void RemoveBackend(std::string_view backend_name);
115
116Backend* GetBackend(std::string_view backend_name);
117 18
118/** 19/**
119 * The global filter will prevent any messages from even being processed if they are filtered. Each 20 * The global filter will prevent any messages from even being processed if they are filtered.
120 * backend can have a filter, but if the level is lower than the global filter, the backend will
121 * never get the message
122 */ 21 */
123void SetGlobalFilter(const Filter& filter); 22void SetGlobalFilter(const Filter& filter);
124} // namespace Common::Log \ No newline at end of file 23
24void SetColorConsoleBackendEnabled(bool enabled);
25} // namespace Common::Log
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index f055f0e11..42744c994 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -111,6 +111,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
111 SUB(Service, NCM) \ 111 SUB(Service, NCM) \
112 SUB(Service, NFC) \ 112 SUB(Service, NFC) \
113 SUB(Service, NFP) \ 113 SUB(Service, NFP) \
114 SUB(Service, NGCT) \
114 SUB(Service, NIFM) \ 115 SUB(Service, NIFM) \
115 SUB(Service, NIM) \ 116 SUB(Service, NIM) \
116 SUB(Service, NPNS) \ 117 SUB(Service, NPNS) \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 8d43eddc7..c186d55ef 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -4,7 +4,11 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <fmt/format.h> 7#include <algorithm>
8#include <string_view>
9
10#include <fmt/core.h>
11
8#include "common/logging/types.h" 12#include "common/logging/types.h"
9 13
10namespace Common::Log { 14namespace Common::Log {
diff --git a/src/common/logging/log_entry.h b/src/common/logging/log_entry.h
new file mode 100644
index 000000000..dd6f44841
--- /dev/null
+++ b/src/common/logging/log_entry.h
@@ -0,0 +1,28 @@
1// Copyright 2021 yuzu 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 <chrono>
8
9#include "common/logging/types.h"
10
11namespace Common::Log {
12
13/**
14 * A log entry. Log entries are store in a structured format to permit more varied output
15 * formatting on different frontends, as well as facilitating filtering and aggregation.
16 */
17struct Entry {
18 std::chrono::microseconds timestamp;
19 Class log_class{};
20 Level log_level{};
21 const char* filename = nullptr;
22 unsigned int line_num = 0;
23 std::string function;
24 std::string message;
25 bool final_entry = false;
26};
27
28} // namespace Common::Log
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index cfc0d5846..10b2281db 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -13,6 +13,7 @@
13#include "common/common_funcs.h" 13#include "common/common_funcs.h"
14#include "common/logging/filter.h" 14#include "common/logging/filter.h"
15#include "common/logging/log.h" 15#include "common/logging/log.h"
16#include "common/logging/log_entry.h"
16#include "common/logging/text_formatter.h" 17#include "common/logging/text_formatter.h"
17#include "common/string_util.h" 18#include "common/string_util.h"
18 19
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index 7ad0334fc..2d21fc483 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -4,8 +4,6 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <chrono>
8
9#include "common/common_types.h" 7#include "common/common_types.h"
10 8
11namespace Common::Log { 9namespace Common::Log {
@@ -81,6 +79,7 @@ enum class Class : u8 {
81 Service_NCM, ///< The NCM service 79 Service_NCM, ///< The NCM service
82 Service_NFC, ///< The NFC (Near-field communication) service 80 Service_NFC, ///< The NFC (Near-field communication) service
83 Service_NFP, ///< The NFP service 81 Service_NFP, ///< The NFP service
82 Service_NGCT, ///< The NGCT (No Good Content for Terra) service
84 Service_NIFM, ///< The NIFM (Network interface) service 83 Service_NIFM, ///< The NIFM (Network interface) service
85 Service_NIM, ///< The NIM service 84 Service_NIM, ///< The NIM service
86 Service_NPNS, ///< The NPNS service 85 Service_NPNS, ///< The NPNS service
@@ -130,19 +129,4 @@ enum class Class : u8 {
130 Count ///< Total number of logging classes 129 Count ///< Total number of logging classes
131}; 130};
132 131
133/**
134 * A log entry. Log entries are store in a structured format to permit more varied output
135 * formatting on different frontends, as well as facilitating filtering and aggregation.
136 */
137struct Entry {
138 std::chrono::microseconds timestamp;
139 Class log_class{};
140 Level log_level{};
141 const char* filename = nullptr;
142 unsigned int line_num = 0;
143 std::string function;
144 std::string message;
145 bool final_entry = false;
146};
147
148} // namespace Common::Log 132} // namespace Common::Log