backend.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright 2014 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <atomic>
  5. #include <chrono>
  6. #include <climits>
  7. #include <exception>
  8. #include <stop_token>
  9. #include <thread>
  10. #include <vector>
  11. #include <fmt/format.h>
  12. #ifdef _WIN32
  13. #include <windows.h> // For OutputDebugStringW
  14. #endif
  15. #include "common/fs/file.h"
  16. #include "common/fs/fs.h"
  17. #include "common/fs/fs_paths.h"
  18. #include "common/fs/path_util.h"
  19. #include "common/literals.h"
  20. #include "common/thread.h"
  21. #include "common/logging/backend.h"
  22. #include "common/logging/log.h"
  23. #include "common/logging/log_entry.h"
  24. #include "common/logging/text_formatter.h"
  25. #include "common/settings.h"
  26. #ifdef _WIN32
  27. #include "common/string_util.h"
  28. #endif
  29. #include "common/threadsafe_queue.h"
  30. namespace Common::Log {
  31. namespace {
  32. /**
  33. * Interface for logging backends.
  34. */
  35. class Backend {
  36. public:
  37. virtual ~Backend() = default;
  38. virtual void Write(const Entry& entry) = 0;
  39. virtual void EnableForStacktrace() = 0;
  40. virtual void Flush() = 0;
  41. };
  42. /**
  43. * Backend that writes to stderr and with color
  44. */
  45. class ColorConsoleBackend final : public Backend {
  46. public:
  47. explicit ColorConsoleBackend() = default;
  48. ~ColorConsoleBackend() override = default;
  49. void Write(const Entry& entry) override {
  50. if (enabled.load(std::memory_order_relaxed)) {
  51. PrintColoredMessage(entry);
  52. }
  53. }
  54. void Flush() override {
  55. // stderr shouldn't be buffered
  56. }
  57. void EnableForStacktrace() override {
  58. enabled = true;
  59. }
  60. void SetEnabled(bool enabled_) {
  61. enabled = enabled_;
  62. }
  63. private:
  64. std::atomic_bool enabled{false};
  65. };
  66. /**
  67. * Backend that writes to a file passed into the constructor
  68. */
  69. class FileBackend final : public Backend {
  70. public:
  71. explicit FileBackend(const std::filesystem::path& filename) {
  72. auto old_filename = filename;
  73. old_filename += ".old.txt";
  74. // Existence checks are done within the functions themselves.
  75. // We don't particularly care if these succeed or not.
  76. static_cast<void>(FS::RemoveFile(old_filename));
  77. static_cast<void>(FS::RenameFile(filename, old_filename));
  78. file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write,
  79. FS::FileType::TextFile);
  80. }
  81. ~FileBackend() override = default;
  82. void Write(const Entry& entry) override {
  83. if (!enabled) {
  84. return;
  85. }
  86. bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
  87. using namespace Common::Literals;
  88. // Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
  89. const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
  90. const bool write_limit_exceeded = bytes_written > write_limit;
  91. if (entry.log_level >= Level::Error || write_limit_exceeded) {
  92. if (write_limit_exceeded) {
  93. // Stop writing after the write limit is exceeded.
  94. // Don't close the file so we can print a stacktrace if necessary
  95. enabled = false;
  96. }
  97. file->Flush();
  98. }
  99. }
  100. void Flush() override {
  101. file->Flush();
  102. }
  103. void EnableForStacktrace() override {
  104. enabled = true;
  105. bytes_written = 0;
  106. }
  107. private:
  108. std::unique_ptr<FS::IOFile> file;
  109. bool enabled = true;
  110. std::size_t bytes_written = 0;
  111. };
  112. /**
  113. * Backend that writes to Visual Studio's output window
  114. */
  115. class DebuggerBackend final : public Backend {
  116. public:
  117. explicit DebuggerBackend() = default;
  118. ~DebuggerBackend() override = default;
  119. void Write(const Entry& entry) override {
  120. #ifdef _WIN32
  121. ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
  122. #endif
  123. }
  124. void Flush() override {}
  125. void EnableForStacktrace() override {}
  126. };
  127. bool initialization_in_progress_suppress_logging = true;
  128. /**
  129. * Static state as a singleton.
  130. */
  131. class Impl {
  132. public:
  133. static Impl& Instance() {
  134. if (!instance) {
  135. throw std::runtime_error("Using Logging instance before its initialization");
  136. }
  137. return *instance;
  138. }
  139. static void Initialize() {
  140. if (instance) {
  141. LOG_WARNING(Log, "Reinitializing logging backend");
  142. return;
  143. }
  144. using namespace Common::FS;
  145. const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
  146. void(CreateDir(log_dir));
  147. Filter filter;
  148. filter.ParseFilterString(Settings::values.log_filter.GetValue());
  149. instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
  150. Deleter);
  151. initialization_in_progress_suppress_logging = false;
  152. }
  153. static void Start() {
  154. instance->StartBackendThread();
  155. }
  156. Impl(const Impl&) = delete;
  157. Impl& operator=(const Impl&) = delete;
  158. Impl(Impl&&) = delete;
  159. Impl& operator=(Impl&&) = delete;
  160. void SetGlobalFilter(const Filter& f) {
  161. filter = f;
  162. }
  163. void SetColorConsoleBackendEnabled(bool enabled) {
  164. color_console_backend.SetEnabled(enabled);
  165. }
  166. void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
  167. const char* function, std::string&& message) {
  168. if (!filter.CheckMessage(log_class, log_level))
  169. return;
  170. const Entry& entry =
  171. CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
  172. message_queue.Push(entry);
  173. }
  174. private:
  175. Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
  176. : filter{filter_}, file_backend{file_backend_filename} {}
  177. ~Impl() {
  178. StopBackendThread();
  179. }
  180. void StartBackendThread() {
  181. backend_thread = std::thread([this] {
  182. Common::SetCurrentThreadName("yuzu:Log");
  183. Entry entry;
  184. const auto write_logs = [this, &entry]() {
  185. ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
  186. };
  187. while (!stop.stop_requested()) {
  188. entry = message_queue.PopWait(stop.get_token());
  189. if (entry.filename != nullptr) {
  190. write_logs();
  191. }
  192. }
  193. // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
  194. // case where a system is repeatedly spamming logs even on close.
  195. int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
  196. while (max_logs_to_write-- && message_queue.Pop(entry)) {
  197. write_logs();
  198. }
  199. });
  200. }
  201. void StopBackendThread() {
  202. stop.request_stop();
  203. backend_thread.join();
  204. }
  205. Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
  206. const char* function, std::string&& message) const {
  207. using std::chrono::duration_cast;
  208. using std::chrono::microseconds;
  209. using std::chrono::steady_clock;
  210. return {
  211. .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
  212. .log_class = log_class,
  213. .log_level = log_level,
  214. .filename = filename,
  215. .line_num = line_nr,
  216. .function = function,
  217. .message = std::move(message),
  218. };
  219. }
  220. void ForEachBackend(auto lambda) {
  221. lambda(static_cast<Backend&>(debugger_backend));
  222. lambda(static_cast<Backend&>(color_console_backend));
  223. lambda(static_cast<Backend&>(file_backend));
  224. }
  225. static void Deleter(Impl* ptr) {
  226. delete ptr;
  227. }
  228. static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
  229. Filter filter;
  230. DebuggerBackend debugger_backend{};
  231. ColorConsoleBackend color_console_backend{};
  232. FileBackend file_backend;
  233. std::stop_source stop;
  234. std::thread backend_thread;
  235. MPSCQueue<Entry, true> message_queue{};
  236. std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
  237. };
  238. } // namespace
  239. void Initialize() {
  240. Impl::Initialize();
  241. }
  242. void Start() {
  243. Impl::Start();
  244. }
  245. void DisableLoggingInTests() {
  246. initialization_in_progress_suppress_logging = true;
  247. }
  248. void SetGlobalFilter(const Filter& filter) {
  249. Impl::Instance().SetGlobalFilter(filter);
  250. }
  251. void SetColorConsoleBackendEnabled(bool enabled) {
  252. Impl::Instance().SetColorConsoleBackendEnabled(enabled);
  253. }
  254. void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
  255. unsigned int line_num, const char* function, const char* format,
  256. const fmt::format_args& args) {
  257. if (!initialization_in_progress_suppress_logging) {
  258. Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
  259. fmt::vformat(format, args));
  260. }
  261. }
  262. } // namespace Common::Log