summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/common_paths.h3
-rw-r--r--src/common/file_util.cpp17
-rw-r--r--src/common/file_util.h11
-rw-r--r--src/common/logging/backend.cpp157
-rw-r--r--src/common/logging/backend.h87
5 files changed, 257 insertions, 18 deletions
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index 0a6132dab..d03fca314 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -32,12 +32,15 @@
32#define SDMC_DIR "sdmc" 32#define SDMC_DIR "sdmc"
33#define NAND_DIR "nand" 33#define NAND_DIR "nand"
34#define SYSDATA_DIR "sysdata" 34#define SYSDATA_DIR "sysdata"
35#define LOG_DIR "log"
35 36
36// Filenames 37// Filenames
37// Files in the directory returned by GetUserPath(D_CONFIG_IDX) 38// Files in the directory returned by GetUserPath(D_CONFIG_IDX)
38#define EMU_CONFIG "emu.ini" 39#define EMU_CONFIG "emu.ini"
39#define DEBUGGER_CONFIG "debugger.ini" 40#define DEBUGGER_CONFIG "debugger.ini"
40#define LOGGER_CONFIG "logger.ini" 41#define LOGGER_CONFIG "logger.ini"
42// Files in the directory returned by GetUserPath(D_LOGS_IDX)
43#define LOG_FILE "citra_log.txt"
41 44
42// Sys files 45// Sys files
43#define SHARED_FONT "shared_font.bin" 46#define SHARED_FONT "shared_font.bin"
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 2152e3fea..b9e1fd1f6 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -713,6 +713,8 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new
713 paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; 713 paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP;
714 paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; 714 paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP;
715 paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; 715 paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP;
716 // TODO: Put the logs in a better location for each OS
717 paths[D_LOGS_IDX] = paths[D_USER_IDX] + LOG_DIR DIR_SEP;
716 } 718 }
717 719
718 if (!newPath.empty()) { 720 if (!newPath.empty()) {
@@ -799,8 +801,8 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
799 801
800IOFile::IOFile() {} 802IOFile::IOFile() {}
801 803
802IOFile::IOFile(const std::string& filename, const char openmode[]) { 804IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
803 Open(filename, openmode); 805 Open(filename, openmode, flags);
804} 806}
805 807
806IOFile::~IOFile() { 808IOFile::~IOFile() {
@@ -821,11 +823,16 @@ void IOFile::Swap(IOFile& other) noexcept {
821 std::swap(m_good, other.m_good); 823 std::swap(m_good, other.m_good);
822} 824}
823 825
824bool IOFile::Open(const std::string& filename, const char openmode[]) { 826bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
825 Close(); 827 Close();
826#ifdef _WIN32 828#ifdef _WIN32
827 _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), 829 if (flags != 0) {
828 Common::UTF8ToUTF16W(openmode).c_str()); 830 m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
831 Common::UTF8ToUTF16W(openmode).c_str(), flags);
832 } else {
833 _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
834 Common::UTF8ToUTF16W(openmode).c_str());
835 }
829#else 836#else
830 m_file = fopen(filename.c_str(), openmode); 837 m_file = fopen(filename.c_str(), openmode);
831#endif 838#endif
diff --git a/src/common/file_util.h b/src/common/file_util.h
index fc6b3ea46..5bc7fbf7c 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -156,7 +156,10 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
156class IOFile : public NonCopyable { 156class IOFile : public NonCopyable {
157public: 157public:
158 IOFile(); 158 IOFile();
159 IOFile(const std::string& filename, const char openmode[]); 159 // flags is used for windows specific file open mode flags, which
160 // allows yuzu to open the logs in shared write mode, so that the file
161 // isn't considered "locked" while yuzu is open and people can open the log file and view it
162 IOFile(const std::string& filename, const char openmode[], int flags = 0);
160 163
161 ~IOFile(); 164 ~IOFile();
162 165
@@ -165,7 +168,7 @@ public:
165 168
166 void Swap(IOFile& other) noexcept; 169 void Swap(IOFile& other) noexcept;
167 170
168 bool Open(const std::string& filename, const char openmode[]); 171 bool Open(const std::string& filename, const char openmode[], int flags = 0);
169 bool Close(); 172 bool Close();
170 173
171 template <typename T> 174 template <typename T>
@@ -220,6 +223,10 @@ public:
220 return WriteArray(&object, 1); 223 return WriteArray(&object, 1);
221 } 224 }
222 225
226 size_t WriteString(const std::string& str) {
227 return WriteArray(str.c_str(), str.length());
228 }
229
223 bool IsOpen() const { 230 bool IsOpen() const {
224 return nullptr != m_file; 231 return nullptr != m_file;
225 } 232 }
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index c26b20062..242914c6a 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -2,16 +2,145 @@
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 <utility> 5#include <algorithm>
6#include <array>
7#include <chrono>
8#include <condition_variable>
9#include <memory>
10#include <thread>
11#ifdef _WIN32
12#include <share.h> // For _SH_DENYWR
13#else
14#define _SH_DENYWR 0
15#endif
6#include "common/assert.h" 16#include "common/assert.h"
17#include "common/common_funcs.h" // snprintf compatibility define
7#include "common/logging/backend.h" 18#include "common/logging/backend.h"
8#include "common/logging/filter.h"
9#include "common/logging/log.h" 19#include "common/logging/log.h"
10#include "common/logging/text_formatter.h" 20#include "common/logging/text_formatter.h"
11#include "common/string_util.h" 21#include "common/string_util.h"
22#include "common/threadsafe_queue.h"
12 23
13namespace Log { 24namespace Log {
14 25
26/**
27 * Static state as a singleton.
28 */
29class Impl {
30public:
31 static Impl& Instance() {
32 static Impl backend;
33 return backend;
34 }
35
36 Impl(Impl const&) = delete;
37 const Impl& operator=(Impl const&) = delete;
38
39 void PushEntry(Entry e) {
40 std::lock_guard<std::mutex> lock(message_mutex);
41 message_queue.Push(std::move(e));
42 message_cv.notify_one();
43 }
44
45 void AddBackend(std::unique_ptr<Backend> backend) {
46 std::lock_guard<std::mutex> lock(writing_mutex);
47 backends.push_back(std::move(backend));
48 }
49
50 void RemoveBackend(const std::string& backend_name) {
51 std::lock_guard<std::mutex> lock(writing_mutex);
52 auto it = std::remove_if(backends.begin(), backends.end(), [&backend_name](const auto& i) {
53 return !strcmp(i->GetName(), backend_name.c_str());
54 });
55 backends.erase(it, backends.end());
56 }
57
58 const Filter& GetGlobalFilter() const {
59 return filter;
60 }
61
62 void SetGlobalFilter(const Filter& f) {
63 filter = f;
64 }
65
66 Backend* GetBackend(const std::string& backend_name) {
67 auto it = std::find_if(backends.begin(), backends.end(), [&backend_name](const auto& i) {
68 return !strcmp(i->GetName(), backend_name.c_str());
69 });
70 if (it == backends.end())
71 return nullptr;
72 return it->get();
73 }
74
75private:
76 Impl() {
77 backend_thread = std::thread([&] {
78 Entry entry;
79 auto write_logs = [&](Entry& e) {
80 std::lock_guard<std::mutex> lock(writing_mutex);
81 for (const auto& backend : backends) {
82 backend->Write(e);
83 }
84 };
85 while (true) {
86 std::unique_lock<std::mutex> lock(message_mutex);
87 message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
88 if (!running) {
89 break;
90 }
91 write_logs(entry);
92 }
93 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
94 // where a system is repeatedly spamming logs even on close.
95 constexpr int MAX_LOGS_TO_WRITE = 100;
96 int logs_written = 0;
97 while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
98 write_logs(entry);
99 }
100 });
101 }
102
103 ~Impl() {
104 running = false;
105 message_cv.notify_one();
106 backend_thread.join();
107 }
108
109 std::atomic_bool running{true};
110 std::mutex message_mutex, writing_mutex;
111 std::condition_variable message_cv;
112 std::thread backend_thread;
113 std::vector<std::unique_ptr<Backend>> backends;
114 Common::MPSCQueue<Log::Entry> message_queue;
115 Filter filter;
116};
117
118void ConsoleBackend::Write(const Entry& entry) {
119 PrintMessage(entry);
120}
121
122void ColorConsoleBackend::Write(const Entry& entry) {
123 PrintColoredMessage(entry);
124}
125
126// _SH_DENYWR allows read only access to the file for other programs.
127// It is #defined to 0 on other platforms
128FileBackend::FileBackend(const std::string& filename)
129 : file(filename, "w", _SH_DENYWR), bytes_written(0) {}
130
131void FileBackend::Write(const Entry& entry) {
132 // prevent logs from going over the maximum size (in case its spamming and the user doesn't
133 // know)
134 constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
135 if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
136 return;
137 }
138 bytes_written += file.WriteString(FormatLogMessage(entry) + '\n');
139 if (entry.log_level >= Level::Error) {
140 file.Flush();
141 }
142}
143
15/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. 144/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
16#define ALL_LOG_CLASSES() \ 145#define ALL_LOG_CLASSES() \
17 CLS(Log) \ 146 CLS(Log) \
@@ -125,20 +254,32 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign
125 return entry; 254 return entry;
126} 255}
127 256
128static Filter* filter = nullptr; 257void SetGlobalFilter(const Filter& filter) {
258 Impl::Instance().SetGlobalFilter(filter);
259}
260
261void AddBackend(std::unique_ptr<Backend> backend) {
262 Impl::Instance().AddBackend(std::move(backend));
263}
129 264
130void SetFilter(Filter* new_filter) { 265void RemoveBackend(const std::string& backend_name) {
131 filter = new_filter; 266 Impl::Instance().RemoveBackend(backend_name);
267}
268
269Backend* GetBackend(const std::string& backend_name) {
270 return Impl::Instance().GetBackend(backend_name);
132} 271}
133 272
134void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, 273void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
135 unsigned int line_num, const char* function, const char* format, 274 unsigned int line_num, const char* function, const char* format,
136 const fmt::format_args& args) { 275 const fmt::format_args& args) {
137 if (filter && !filter->CheckMessage(log_class, log_level)) 276 auto filter = Impl::Instance().GetGlobalFilter();
277 if (!filter.CheckMessage(log_class, log_level))
138 return; 278 return;
279
139 Entry entry = 280 Entry entry =
140 CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); 281 CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
141 282
142 PrintColoredMessage(entry); 283 Impl::Instance().PushEntry(std::move(entry));
143} 284}
144} // namespace Log 285} // namespace Log \ No newline at end of file
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index 7e81efb23..57cdf6b2d 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -1,13 +1,15 @@
1// Copyright 2014 Citra Emulator Project 1// Copyright 2014 Citra Emulator Project
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
5#pragma once 4#pragma once
6 5
7#include <chrono> 6#include <chrono>
8#include <cstdarg> 7#include <cstdarg>
8#include <memory>
9#include <string> 9#include <string>
10#include <utility> 10#include <utility>
11#include "common/file_util.h"
12#include "common/logging/filter.h"
11#include "common/logging/log.h" 13#include "common/logging/log.h"
12 14
13namespace Log { 15namespace Log {
@@ -35,6 +37,80 @@ struct Entry {
35}; 37};
36 38
37/** 39/**
40 * Interface for logging backends. As loggers can be created and removed at runtime, this can be
41 * used by a frontend for adding a custom logging backend as needed
42 */
43class Backend {
44public:
45 virtual ~Backend() = default;
46 virtual void SetFilter(const Filter& new_filter) {
47 filter = new_filter;
48 }
49 virtual const char* GetName() const = 0;
50 virtual void Write(const Entry& entry) = 0;
51
52private:
53 Filter filter;
54};
55
56/**
57 * Backend that writes to stderr without any color commands
58 */
59class ConsoleBackend : public Backend {
60public:
61 static const char* Name() {
62 return "console";
63 }
64 const char* GetName() const override {
65 return Name();
66 }
67 void Write(const Entry& entry) override;
68};
69
70/**
71 * Backend that writes to stderr and with color
72 */
73class ColorConsoleBackend : public Backend {
74public:
75 static const char* Name() {
76 return "color_console";
77 }
78
79 const char* GetName() const override {
80 return Name();
81 }
82 void Write(const Entry& entry) override;
83};
84
85/**
86 * Backend that writes to a file passed into the constructor
87 */
88class FileBackend : public Backend {
89public:
90 explicit FileBackend(const std::string& filename);
91
92 static const char* Name() {
93 return "file";
94 }
95
96 const char* GetName() const override {
97 return Name();
98 }
99
100 void Write(const Entry& entry) override;
101
102private:
103 FileUtil::IOFile file;
104 size_t bytes_written;
105};
106
107void AddBackend(std::unique_ptr<Backend> backend);
108
109void RemoveBackend(const std::string& backend_name);
110
111Backend* GetBackend(const std::string& backend_name);
112
113/**
38 * Returns the name of the passed log class as a C-string. Subclasses are separated by periods 114 * Returns the name of the passed log class as a C-string. Subclasses are separated by periods
39 * instead of underscores as in the enumeration. 115 * instead of underscores as in the enumeration.
40 */ 116 */
@@ -49,5 +125,10 @@ const char* GetLevelName(Level log_level);
49Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, 125Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
50 const char* function, std::string message); 126 const char* function, std::string message);
51 127
52void SetFilter(Filter* filter); 128/**
53} // namespace Log 129 * The global filter will prevent any messages from even being processed if they are filtered. Each
130 * backend can have a filter, but if the level is lower than the global filter, the backend will
131 * never get the message
132 */
133void SetGlobalFilter(const Filter& filter);
134} // namespace Log \ No newline at end of file