backend.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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() = default;
  178. void StartBackendThread() {
  179. backend_thread = std::jthread([this](std::stop_token stop_token) {
  180. Common::SetCurrentThreadName("yuzu:Log");
  181. Entry entry;
  182. const auto write_logs = [this, &entry]() {
  183. ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
  184. };
  185. while (!stop_token.stop_requested()) {
  186. entry = message_queue.PopWait(stop_token);
  187. if (entry.filename != nullptr) {
  188. write_logs();
  189. }
  190. }
  191. // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
  192. // case where a system is repeatedly spamming logs even on close.
  193. int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
  194. while (max_logs_to_write-- && message_queue.Pop(entry)) {
  195. write_logs();
  196. }
  197. });
  198. }
  199. Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
  200. const char* function, std::string&& message) const {
  201. using std::chrono::duration_cast;
  202. using std::chrono::microseconds;
  203. using std::chrono::steady_clock;
  204. return {
  205. .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
  206. .log_class = log_class,
  207. .log_level = log_level,
  208. .filename = filename,
  209. .line_num = line_nr,
  210. .function = function,
  211. .message = std::move(message),
  212. };
  213. }
  214. void ForEachBackend(auto lambda) {
  215. lambda(static_cast<Backend&>(debugger_backend));
  216. lambda(static_cast<Backend&>(color_console_backend));
  217. lambda(static_cast<Backend&>(file_backend));
  218. }
  219. static void Deleter(Impl* ptr) {
  220. delete ptr;
  221. }
  222. static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
  223. Filter filter;
  224. DebuggerBackend debugger_backend{};
  225. ColorConsoleBackend color_console_backend{};
  226. FileBackend file_backend;
  227. MPSCQueue<Entry, true> message_queue{};
  228. std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
  229. std::jthread backend_thread;
  230. };
  231. } // namespace
  232. void Initialize() {
  233. Impl::Initialize();
  234. }
  235. void Start() {
  236. Impl::Start();
  237. }
  238. void DisableLoggingInTests() {
  239. initialization_in_progress_suppress_logging = true;
  240. }
  241. void SetGlobalFilter(const Filter& filter) {
  242. Impl::Instance().SetGlobalFilter(filter);
  243. }
  244. void SetColorConsoleBackendEnabled(bool enabled) {
  245. Impl::Instance().SetColorConsoleBackendEnabled(enabled);
  246. }
  247. void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
  248. unsigned int line_num, const char* function, const char* format,
  249. const fmt::format_args& args) {
  250. if (!initialization_in_progress_suppress_logging) {
  251. Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
  252. fmt::vformat(format, args));
  253. }
  254. }
  255. } // namespace Common::Log