backend.cpp 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. // SPDX-FileCopyrightText: 2014 Citra Emulator Project & 2024 suyu 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 = GetSuyuPath(SuyuPath::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. static void Stop() {
  169. instance->StopBackendThread();
  170. }
  171. Impl(const Impl&) = delete;
  172. Impl& operator=(const Impl&) = delete;
  173. Impl(Impl&&) = delete;
  174. Impl& operator=(Impl&&) = delete;
  175. void SetGlobalFilter(const Filter& f) {
  176. filter = f;
  177. }
  178. void SetColorConsoleBackendEnabled(bool enabled) {
  179. color_console_backend.SetEnabled(enabled);
  180. }
  181. void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
  182. const char* function, std::string&& message) {
  183. if (!filter.CheckMessage(log_class, log_level)) {
  184. return;
  185. }
  186. message_queue.EmplaceWait(
  187. CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
  188. }
  189. private:
  190. Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
  191. : filter{filter_}, file_backend{file_backend_filename} {}
  192. ~Impl() = default;
  193. void StartBackendThread() {
  194. backend_thread = std::jthread([this](std::stop_token stop_token) {
  195. Common::SetCurrentThreadName("Logger");
  196. Entry entry;
  197. const auto write_logs = [this, &entry]() {
  198. ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
  199. };
  200. while (!stop_token.stop_requested()) {
  201. message_queue.PopWait(entry, stop_token);
  202. if (entry.filename != nullptr) {
  203. write_logs();
  204. }
  205. }
  206. // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
  207. // case where a system is repeatedly spamming logs even on close.
  208. int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
  209. while (max_logs_to_write-- && message_queue.TryPop(entry)) {
  210. write_logs();
  211. }
  212. });
  213. }
  214. void StopBackendThread() {
  215. backend_thread.request_stop();
  216. if (backend_thread.joinable()) {
  217. backend_thread.join();
  218. }
  219. ForEachBackend([](Backend& backend) { backend.Flush(); });
  220. }
  221. Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
  222. const char* function, std::string&& message) const {
  223. using std::chrono::duration_cast;
  224. using std::chrono::microseconds;
  225. using std::chrono::steady_clock;
  226. return {
  227. .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
  228. .log_class = log_class,
  229. .log_level = log_level,
  230. .filename = filename,
  231. .line_num = line_nr,
  232. .function = function,
  233. .message = std::move(message),
  234. };
  235. }
  236. void ForEachBackend(auto lambda) {
  237. lambda(static_cast<Backend&>(debugger_backend));
  238. lambda(static_cast<Backend&>(color_console_backend));
  239. lambda(static_cast<Backend&>(file_backend));
  240. #ifdef ANDROID
  241. lambda(static_cast<Backend&>(lc_backend));
  242. #endif
  243. }
  244. static void Deleter(Impl* ptr) {
  245. delete ptr;
  246. }
  247. static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
  248. Filter filter;
  249. DebuggerBackend debugger_backend{};
  250. ColorConsoleBackend color_console_backend{};
  251. FileBackend file_backend;
  252. #ifdef ANDROID
  253. LogcatBackend lc_backend{};
  254. #endif
  255. MPSCQueue<Entry> message_queue{};
  256. std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
  257. std::jthread backend_thread;
  258. };
  259. } // namespace
  260. void Initialize() {
  261. Impl::Initialize();
  262. }
  263. void Start() {
  264. Impl::Start();
  265. }
  266. void Stop() {
  267. Impl::Stop();
  268. }
  269. void DisableLoggingInTests() {
  270. initialization_in_progress_suppress_logging = true;
  271. }
  272. void SetGlobalFilter(const Filter& filter) {
  273. Impl::Instance().SetGlobalFilter(filter);
  274. }
  275. void SetColorConsoleBackendEnabled(bool enabled) {
  276. Impl::Instance().SetColorConsoleBackendEnabled(enabled);
  277. }
  278. void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
  279. unsigned int line_num, const char* function, fmt::string_view format,
  280. const fmt::format_args& args) {
  281. if (!initialization_in_progress_suppress_logging) {
  282. Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
  283. fmt::vformat(format, args));
  284. }
  285. }
  286. } // namespace Common::Log