1 // Copyright 2014 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <algorithm>
6 #include <atomic>
7 #include <chrono>
8 #include <climits>
9 #include <condition_variable>
10 #include <memory>
11 #include <mutex>
12 #include <thread>
13 #include <vector>
14 #ifdef _WIN32
15 #include <share.h> // For _SH_DENYWR
16 #include <windows.h> // For OutputDebugStringW
17 #else
18 #define _SH_DENYWR 0
19 #endif
20 #include "common/assert.h"
21 #include "common/logging/backend.h"
22 #include "common/logging/log.h"
23 #include "common/logging/text_formatter.h"
24 #include "common/string_util.h"
25 #include "common/threadsafe_queue.h"
26 #include "core/settings.h"
27
28 namespace Log {
29
30 /**
31 * Static state as a singleton.
32 */
33 class Impl {
34 public:
Instance()35 static Impl& Instance() {
36 static Impl backend;
37 return backend;
38 }
39
40 Impl(Impl const&) = delete;
41 const Impl& operator=(Impl const&) = delete;
42
PushEntry(Class log_class,Level log_level,const char * filename,unsigned int line_num,const char * function,std::string message)43 void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
44 const char* function, std::string message) {
45 message_queue.Push(
46 CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
47 }
48
AddBackend(std::unique_ptr<Backend> backend)49 void AddBackend(std::unique_ptr<Backend> backend) {
50 std::lock_guard lock{writing_mutex};
51 backends.push_back(std::move(backend));
52 }
53
RemoveBackend(std::string_view backend_name)54 void RemoveBackend(std::string_view backend_name) {
55 std::lock_guard lock{writing_mutex};
56 const auto it =
57 std::remove_if(backends.begin(), backends.end(),
58 [&backend_name](const auto& i) { return backend_name == i->GetName(); });
59 backends.erase(it, backends.end());
60 }
61
GetGlobalFilter() const62 const Filter& GetGlobalFilter() const {
63 return filter;
64 }
65
SetGlobalFilter(const Filter & f)66 void SetGlobalFilter(const Filter& f) {
67 filter = f;
68 }
69
GetBackend(std::string_view backend_name)70 Backend* GetBackend(std::string_view backend_name) {
71 const auto it =
72 std::find_if(backends.begin(), backends.end(),
73 [&backend_name](const auto& i) { return backend_name == i->GetName(); });
74 if (it == backends.end())
75 return nullptr;
76 return it->get();
77 }
78
79 private:
Impl()80 Impl() {
81 backend_thread = std::thread([&] {
82 Entry entry;
83 auto write_logs = [&](Entry& e) {
84 std::lock_guard lock{writing_mutex};
85 for (const auto& backend : backends) {
86 backend->Write(e);
87 }
88 };
89 while (true) {
90 entry = message_queue.PopWait();
91 if (entry.final_entry) {
92 break;
93 }
94 write_logs(entry);
95 }
96
97 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
98 // where a system is repeatedly spamming logs even on close.
99 const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
100 int logs_written = 0;
101 while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
102 write_logs(entry);
103 }
104 });
105 }
106
~Impl()107 ~Impl() {
108 Entry entry;
109 entry.final_entry = true;
110 message_queue.Push(entry);
111 backend_thread.join();
112 }
113
CreateEntry(Class log_class,Level log_level,const char * filename,unsigned int line_nr,const char * function,std::string message) const114 Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
115 const char* function, std::string message) const {
116 using std::chrono::duration_cast;
117 using std::chrono::microseconds;
118 using std::chrono::steady_clock;
119
120 return {
121 .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
122 .log_class = log_class,
123 .log_level = log_level,
124 .filename = filename,
125 .line_num = line_nr,
126 .function = function,
127 .message = std::move(message),
128 .final_entry = false,
129 };
130 }
131
132 std::mutex writing_mutex;
133 std::thread backend_thread;
134 std::vector<std::unique_ptr<Backend>> backends;
135 Common::MPSCQueue<Log::Entry> message_queue;
136 Filter filter;
137 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
138 };
139
Write(const Entry & entry)140 void ConsoleBackend::Write(const Entry& entry) {
141 PrintMessage(entry);
142 }
143
Write(const Entry & entry)144 void ColorConsoleBackend::Write(const Entry& entry) {
145 PrintColoredMessage(entry);
146 }
147
148 // _SH_DENYWR allows read only access to the file for other programs.
149 // It is #defined to 0 on other platforms
FileBackend(const std::string & filename)150 FileBackend::FileBackend(const std::string& filename)
151 : file(filename, "w", _SH_DENYWR), bytes_written(0) {}
152
Write(const Entry & entry)153 void FileBackend::Write(const Entry& entry) {
154 // prevent logs from going over the maximum size (in case its spamming and the user doesn't
155 // know)
156 constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024;
157 constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024;
158
159 if (!file.IsOpen()) {
160 return;
161 }
162
163 if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) {
164 return;
165 } else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) {
166 return;
167 }
168
169 bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
170 if (entry.log_level >= Level::Error) {
171 file.Flush();
172 }
173 }
174
Write(const Entry & entry)175 void DebuggerBackend::Write(const Entry& entry) {
176 #ifdef _WIN32
177 ::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
178 #endif
179 }
180
181 /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
182 #define ALL_LOG_CLASSES() \
183 CLS(Log) \
184 CLS(Common) \
185 SUB(Common, Filesystem) \
186 SUB(Common, Memory) \
187 CLS(Core) \
188 SUB(Core, ARM) \
189 SUB(Core, Timing) \
190 CLS(Config) \
191 CLS(Debug) \
192 SUB(Debug, Emulated) \
193 SUB(Debug, GPU) \
194 SUB(Debug, Breakpoint) \
195 SUB(Debug, GDBStub) \
196 CLS(Kernel) \
197 SUB(Kernel, SVC) \
198 CLS(Service) \
199 SUB(Service, ACC) \
200 SUB(Service, Audio) \
201 SUB(Service, AM) \
202 SUB(Service, AOC) \
203 SUB(Service, APM) \
204 SUB(Service, ARP) \
205 SUB(Service, BCAT) \
206 SUB(Service, BPC) \
207 SUB(Service, BTDRV) \
208 SUB(Service, BTM) \
209 SUB(Service, Capture) \
210 SUB(Service, ERPT) \
211 SUB(Service, ETicket) \
212 SUB(Service, EUPLD) \
213 SUB(Service, Fatal) \
214 SUB(Service, FGM) \
215 SUB(Service, Friend) \
216 SUB(Service, FS) \
217 SUB(Service, GRC) \
218 SUB(Service, HID) \
219 SUB(Service, IRS) \
220 SUB(Service, LBL) \
221 SUB(Service, LDN) \
222 SUB(Service, LDR) \
223 SUB(Service, LM) \
224 SUB(Service, Migration) \
225 SUB(Service, Mii) \
226 SUB(Service, MM) \
227 SUB(Service, NCM) \
228 SUB(Service, NFC) \
229 SUB(Service, NFP) \
230 SUB(Service, NIFM) \
231 SUB(Service, NIM) \
232 SUB(Service, NPNS) \
233 SUB(Service, NS) \
234 SUB(Service, NVDRV) \
235 SUB(Service, OLSC) \
236 SUB(Service, PCIE) \
237 SUB(Service, PCTL) \
238 SUB(Service, PCV) \
239 SUB(Service, PM) \
240 SUB(Service, PREPO) \
241 SUB(Service, PSC) \
242 SUB(Service, PSM) \
243 SUB(Service, SET) \
244 SUB(Service, SM) \
245 SUB(Service, SPL) \
246 SUB(Service, SSL) \
247 SUB(Service, TCAP) \
248 SUB(Service, Time) \
249 SUB(Service, USB) \
250 SUB(Service, VI) \
251 SUB(Service, WLAN) \
252 CLS(HW) \
253 SUB(HW, Memory) \
254 SUB(HW, LCD) \
255 SUB(HW, GPU) \
256 SUB(HW, AES) \
257 CLS(IPC) \
258 CLS(Frontend) \
259 CLS(Render) \
260 SUB(Render, Software) \
261 SUB(Render, OpenGL) \
262 SUB(Render, Vulkan) \
263 CLS(Audio) \
264 SUB(Audio, DSP) \
265 SUB(Audio, Sink) \
266 CLS(Input) \
267 CLS(Network) \
268 CLS(Loader) \
269 CLS(CheatEngine) \
270 CLS(Crypto) \
271 CLS(WebService)
272
273 // GetClassName is a macro defined by Windows.h, grrr...
GetLogClassName(Class log_class)274 const char* GetLogClassName(Class log_class) {
275 switch (log_class) {
276 #define CLS(x) \
277 case Class::x: \
278 return #x;
279 #define SUB(x, y) \
280 case Class::x##_##y: \
281 return #x "." #y;
282 ALL_LOG_CLASSES()
283 #undef CLS
284 #undef SUB
285 case Class::Count:
286 break;
287 }
288 return "Invalid";
289 }
290
GetLevelName(Level log_level)291 const char* GetLevelName(Level log_level) {
292 #define LVL(x) \
293 case Level::x: \
294 return #x
295 switch (log_level) {
296 LVL(Trace);
297 LVL(Debug);
298 LVL(Info);
299 LVL(Warning);
300 LVL(Error);
301 LVL(Critical);
302 case Level::Count:
303 break;
304 }
305 #undef LVL
306 return "Invalid";
307 }
308
SetGlobalFilter(const Filter & filter)309 void SetGlobalFilter(const Filter& filter) {
310 Impl::Instance().SetGlobalFilter(filter);
311 }
312
AddBackend(std::unique_ptr<Backend> backend)313 void AddBackend(std::unique_ptr<Backend> backend) {
314 Impl::Instance().AddBackend(std::move(backend));
315 }
316
RemoveBackend(std::string_view backend_name)317 void RemoveBackend(std::string_view backend_name) {
318 Impl::Instance().RemoveBackend(backend_name);
319 }
320
GetBackend(std::string_view backend_name)321 Backend* GetBackend(std::string_view backend_name) {
322 return Impl::Instance().GetBackend(backend_name);
323 }
324
FmtLogMessageImpl(Class log_class,Level log_level,const char * filename,unsigned int line_num,const char * function,const char * format,const fmt::format_args & args)325 void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
326 unsigned int line_num, const char* function, const char* format,
327 const fmt::format_args& args) {
328 auto& instance = Impl::Instance();
329 const auto& filter = instance.GetGlobalFilter();
330 if (!filter.CheckMessage(log_class, log_level))
331 return;
332
333 instance.PushEntry(log_class, log_level, filename, line_num, function,
334 fmt::vformat(format, args));
335 }
336 } // namespace Log
337