backend.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2014 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <algorithm>
  5. #include <atomic>
  6. #include <chrono>
  7. #include <climits>
  8. #include <condition_variable>
  9. #include <memory>
  10. #include <mutex>
  11. #include <thread>
  12. #include <vector>
  13. #ifdef _WIN32
  14. #include <windows.h> // For OutputDebugStringW
  15. #endif
  16. #include "common/assert.h"
  17. #include "common/fs/file.h"
  18. #include "common/fs/fs.h"
  19. #include "common/logging/backend.h"
  20. #include "common/logging/log.h"
  21. #include "common/logging/text_formatter.h"
  22. #include "common/settings.h"
  23. #include "common/string_util.h"
  24. #include "common/threadsafe_queue.h"
  25. namespace Common::Log {
  26. /**
  27. * Static state as a singleton.
  28. */
  29. class Impl {
  30. public:
  31. static Impl& Instance() {
  32. static Impl backend;
  33. return backend;
  34. }
  35. Impl(const Impl&) = delete;
  36. Impl& operator=(const Impl&) = delete;
  37. Impl(Impl&&) = delete;
  38. Impl& operator=(Impl&&) = delete;
  39. void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
  40. const char* function, std::string message) {
  41. message_queue.Push(
  42. CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
  43. }
  44. void AddBackend(std::unique_ptr<Backend> backend) {
  45. std::lock_guard lock{writing_mutex};
  46. backends.push_back(std::move(backend));
  47. }
  48. void RemoveBackend(std::string_view backend_name) {
  49. std::lock_guard lock{writing_mutex};
  50. std::erase_if(backends, [&backend_name](const auto& backend) {
  51. return backend_name == backend->GetName();
  52. });
  53. }
  54. const Filter& GetGlobalFilter() const {
  55. return filter;
  56. }
  57. void SetGlobalFilter(const Filter& f) {
  58. filter = f;
  59. }
  60. Backend* GetBackend(std::string_view backend_name) {
  61. const auto it =
  62. std::find_if(backends.begin(), backends.end(),
  63. [&backend_name](const auto& i) { return backend_name == i->GetName(); });
  64. if (it == backends.end())
  65. return nullptr;
  66. return it->get();
  67. }
  68. private:
  69. Impl() {
  70. backend_thread = std::thread([&] {
  71. Entry entry;
  72. auto write_logs = [&](Entry& e) {
  73. std::lock_guard lock{writing_mutex};
  74. for (const auto& backend : backends) {
  75. backend->Write(e);
  76. }
  77. };
  78. while (true) {
  79. entry = message_queue.PopWait();
  80. if (entry.final_entry) {
  81. break;
  82. }
  83. write_logs(entry);
  84. }
  85. // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
  86. // where a system is repeatedly spamming logs even on close.
  87. const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
  88. int logs_written = 0;
  89. while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
  90. write_logs(entry);
  91. }
  92. });
  93. }
  94. ~Impl() {
  95. Entry entry;
  96. entry.final_entry = true;
  97. message_queue.Push(entry);
  98. backend_thread.join();
  99. }
  100. Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
  101. const char* function, std::string message) const {
  102. using std::chrono::duration_cast;
  103. using std::chrono::microseconds;
  104. using std::chrono::steady_clock;
  105. return {
  106. .timestamp = duration_cast<microseconds>(steady_clock::now() - time_origin),
  107. .log_class = log_class,
  108. .log_level = log_level,
  109. .filename = filename,
  110. .line_num = line_nr,
  111. .function = function,
  112. .message = std::move(message),
  113. .final_entry = false,
  114. };
  115. }
  116. std::mutex writing_mutex;
  117. std::thread backend_thread;
  118. std::vector<std::unique_ptr<Backend>> backends;
  119. MPSCQueue<Entry> message_queue;
  120. Filter filter;
  121. std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
  122. };
  123. ConsoleBackend::~ConsoleBackend() = default;
  124. void ConsoleBackend::Write(const Entry& entry) {
  125. PrintMessage(entry);
  126. }
  127. ColorConsoleBackend::~ColorConsoleBackend() = default;
  128. void ColorConsoleBackend::Write(const Entry& entry) {
  129. PrintColoredMessage(entry);
  130. }
  131. FileBackend::FileBackend(const std::filesystem::path& filename) {
  132. auto old_filename = filename;
  133. old_filename += ".old.txt";
  134. // Existence checks are done within the functions themselves.
  135. // We don't particularly care if these succeed or not.
  136. void(FS::RemoveFile(old_filename));
  137. void(FS::RenameFile(filename, old_filename));
  138. file =
  139. std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
  140. }
  141. FileBackend::~FileBackend() = default;
  142. void FileBackend::Write(const Entry& entry) {
  143. // prevent logs from going over the maximum size (in case its spamming and the user doesn't
  144. // know)
  145. constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024;
  146. constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024;
  147. if (!file->IsOpen()) {
  148. return;
  149. }
  150. if (Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN_EXTENDED) {
  151. return;
  152. } else if (!Settings::values.extended_logging && bytes_written > MAX_BYTES_WRITTEN) {
  153. return;
  154. }
  155. bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
  156. if (entry.log_level >= Level::Error) {
  157. void(file->Flush());
  158. }
  159. }
  160. DebuggerBackend::~DebuggerBackend() = default;
  161. void DebuggerBackend::Write(const Entry& entry) {
  162. #ifdef _WIN32
  163. ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
  164. #endif
  165. }
  166. void SetGlobalFilter(const Filter& filter) {
  167. Impl::Instance().SetGlobalFilter(filter);
  168. }
  169. void AddBackend(std::unique_ptr<Backend> backend) {
  170. Impl::Instance().AddBackend(std::move(backend));
  171. }
  172. void RemoveBackend(std::string_view backend_name) {
  173. Impl::Instance().RemoveBackend(backend_name);
  174. }
  175. Backend* GetBackend(std::string_view backend_name) {
  176. return Impl::Instance().GetBackend(backend_name);
  177. }
  178. void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
  179. unsigned int line_num, const char* function, const char* format,
  180. const fmt::format_args& args) {
  181. auto& instance = Impl::Instance();
  182. const auto& filter = instance.GetGlobalFilter();
  183. if (!filter.CheckMessage(log_class, log_level))
  184. return;
  185. instance.PushEntry(log_class, log_level, filename, line_num, function,
  186. fmt::vformat(format, args));
  187. }
  188. } // namespace Common::Log