backend.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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.GetValue() ? 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. #ifdef ANDROID
  125. /**
  126. * Backend that writes to the Android logcat
  127. */
  128. class LogcatBackend : public Backend {
  129. public:
  130. explicit LogcatBackend() = default;
  131. ~LogcatBackend() override = default;
  132. void Write(const Entry& entry) override {
  133. PrintMessageToLogcat(entry);
  134. }
  135. void Flush() override {}
  136. void EnableForStacktrace() override {}
  137. };
  138. #endif
  139. bool initialization_in_progress_suppress_logging = true;
  140. /**
  141. * Static state as a singleton.
  142. */
  143. class Impl {
  144. public:
  145. static Impl& Instance() {
  146. if (!instance) {
  147. throw std::runtime_error("Using Logging instance before its initialization");
  148. }
  149. return *instance;
  150. }
  151. static void Initialize() {
  152. if (instance) {
  153. LOG_WARNING(Log, "Reinitializing logging backend");
  154. return;
  155. }
  156. using namespace Common::FS;
  157. const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
  158. void(CreateDir(log_dir));
  159. Filter filter;
  160. filter.ParseFilterString(Settings::values.log_filter.GetValue());
  161. instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
  162. Deleter);
  163. initialization_in_progress_suppress_logging = false;
  164. }
  165. static void Start() {
  166. instance->StartBackendThread();
  167. }
  168. Impl(const Impl&) = delete;
  169. Impl& operator=(const Impl&) = delete;
  170. Impl(Impl&&) = delete;
  171. Impl& operator=(Impl&&) = delete;
  172. void SetGlobalFilter(const Filter& f) {
  173. filter = f;
  174. }
  175. void SetColorConsoleBackendEnabled(bool enabled) {
  176. color_console_backend.SetEnabled(enabled);
  177. }
  178. void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
  179. const char* function, std::string&& message) {
  180. if (!filter.CheckMessage(log_class, log_level)) {
  181. return;
  182. }
  183. message_queue.EmplaceWait(
  184. CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
  185. }
  186. private:
  187. Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
  188. : filter{filter_}, file_backend{file_backend_filename} {}
  189. ~Impl() = default;
  190. void StartBackendThread() {
  191. backend_thread = std::jthread([this](std::stop_token stop_token) {
  192. Common::SetCurrentThreadName("Logger");
  193. Entry entry;
  194. const auto write_logs = [this, &entry]() {
  195. ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
  196. };
  197. while (!stop_token.stop_requested()) {
  198. message_queue.PopWait(entry, stop_token);
  199. if (entry.filename != nullptr) {
  200. write_logs();
  201. }
  202. }
  203. // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
  204. // case where a system is repeatedly spamming logs even on close.
  205. int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
  206. while (max_logs_to_write-- && message_queue.TryPop(entry)) {
  207. write_logs();
  208. }
  209. });
  210. }
  211. Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
  212. const char* function, std::string&& message) const {
  213. using std::chrono::duration_cast;
  214. using std::chrono::microseconds;
  215. using std::chrono::steady_clock;
  216. return {
  217. .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
  218. .log_class = log_class,
  219. .log_level = log_level,
  220. .filename = filename,
  221. .line_num = line_nr,
  222. .function = function,
  223. .message = std::move(message),
  224. };
  225. }
  226. void ForEachBackend(auto lambda) {
  227. lambda(static_cast<Backend&>(debugger_backend));
  228. lambda(static_cast<Backend&>(color_console_backend));
  229. lambda(static_cast<Backend&>(file_backend));
  230. #ifdef ANDROID
  231. lambda(static_cast<Backend&>(lc_backend));
  232. #endif
  233. }
  234. static void Deleter(Impl* ptr) {
  235. delete ptr;
  236. }
  237. static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
  238. Filter filter;
  239. DebuggerBackend debugger_backend{};
  240. ColorConsoleBackend color_console_backend{};
  241. FileBackend file_backend;
  242. #ifdef ANDROID
  243. LogcatBackend lc_backend{};
  244. #endif
  245. MPSCQueue<Entry> message_queue{};
  246. std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
  247. std::jthread backend_thread;
  248. };
  249. } // namespace
  250. void Initialize() {
  251. Impl::Initialize();
  252. }
  253. void Start() {
  254. Impl::Start();
  255. }
  256. void DisableLoggingInTests() {
  257. initialization_in_progress_suppress_logging = true;
  258. }
  259. void SetGlobalFilter(const Filter& filter) {
  260. Impl::Instance().SetGlobalFilter(filter);
  261. }
  262. void SetColorConsoleBackendEnabled(bool enabled) {
  263. Impl::Instance().SetColorConsoleBackendEnabled(enabled);
  264. }
  265. void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
  266. unsigned int line_num, const char* function, const char* format,
  267. const fmt::format_args& args) {
  268. if (!initialization_in_progress_suppress_logging) {
  269. Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
  270. fmt::vformat(format, args));
  271. }
  272. }
  273. } // namespace Common::Log