backend.cpp 8.7 KB

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