logging: Simplify and make thread-safe
This simplifies the logging system. This also fixes some lost messages on startup. The simplification is simple. I removed unused functions and moved most things in the .h to the .cpp. I replaced the unnecessary linked list with its contents laid out as three member variables. Anything that went through the linked list now directly accesses the backends. Generic functions are replaced with those for each specific use case and there aren't many. This change increases coupling but we gain back more KISS and encapsulation. With those changes it was easy to make it thread-safe. I just removed the mutex and turned a boolean atomic. I was planning to use this thread-safety in my next PR about stacktraces. It was actually async-signal-safety at first but I ended up using a different approach. Anyway getting rid of the linked list is important for that because have the list of backends constantly changing complicates things.
This commit is contained in:
parent
8e8ca7d9d0
commit
3641b9891d
|
@ -174,25 +174,10 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
|
||||||
std::cout << std::endl << "* " << message << std::endl << std::endl;
|
std::cout << std::endl << "* " << message << std::endl << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void InitializeLogging() {
|
|
||||||
using namespace Common;
|
|
||||||
|
|
||||||
Log::Filter log_filter(Log::Level::Debug);
|
|
||||||
log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
|
||||||
Log::SetGlobalFilter(log_filter);
|
|
||||||
|
|
||||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
|
||||||
|
|
||||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
|
||||||
FileUtil::CreateFullPath(log_dir);
|
|
||||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
|
||||||
#ifdef _WIN32
|
|
||||||
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Application entry point
|
/// Application entry point
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
Common::Log::Initialize();
|
||||||
|
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||||
Common::DetachedTasks detached_tasks;
|
Common::DetachedTasks detached_tasks;
|
||||||
Config config;
|
Config config;
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -203,8 +188,6 @@ int main(int argc, char** argv) {
|
||||||
std::string movie_play;
|
std::string movie_play;
|
||||||
std::string dump_video;
|
std::string dump_video;
|
||||||
|
|
||||||
InitializeLogging();
|
|
||||||
|
|
||||||
char* endarg;
|
char* endarg;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
int argc_w;
|
int argc_w;
|
||||||
|
@ -346,6 +329,7 @@ int main(int argc, char** argv) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Core::System::InitializeGlobalInstance();
|
||||||
auto& system = Core::System::GetInstance();
|
auto& system = Core::System::GetInstance();
|
||||||
auto& movie = Core::Movie::GetInstance();
|
auto& movie = Core::Movie::GetInstance();
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ void ToggleConsole() {
|
||||||
console_shown = UISettings::values.show_console.GetValue();
|
console_shown = UISettings::values.show_console.GetValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using namespace Common::Log;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
FILE* temp;
|
FILE* temp;
|
||||||
if (UISettings::values.show_console) {
|
if (UISettings::values.show_console) {
|
||||||
|
@ -29,24 +30,20 @@ void ToggleConsole() {
|
||||||
freopen_s(&temp, "CONIN$", "r", stdin);
|
freopen_s(&temp, "CONIN$", "r", stdin);
|
||||||
freopen_s(&temp, "CONOUT$", "w", stdout);
|
freopen_s(&temp, "CONOUT$", "w", stdout);
|
||||||
freopen_s(&temp, "CONOUT$", "w", stderr);
|
freopen_s(&temp, "CONOUT$", "w", stderr);
|
||||||
Common::Log::AddBackend(std::make_unique<Common::Log::ColorConsoleBackend>());
|
SetColorConsoleBackendEnabled(true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (FreeConsole()) {
|
if (FreeConsole()) {
|
||||||
// In order to close the console, we have to also detach the streams on it.
|
// In order to close the console, we have to also detach the streams on it.
|
||||||
// Just redirect them to NUL if there is no console window
|
// Just redirect them to NUL if there is no console window
|
||||||
Common::Log::RemoveBackend(Common::Log::ColorConsoleBackend::Name());
|
SetColorConsoleBackendEnabled(false);
|
||||||
freopen_s(&temp, "NUL", "r", stdin);
|
freopen_s(&temp, "NUL", "r", stdin);
|
||||||
freopen_s(&temp, "NUL", "w", stdout);
|
freopen_s(&temp, "NUL", "w", stdout);
|
||||||
freopen_s(&temp, "NUL", "w", stderr);
|
freopen_s(&temp, "NUL", "w", stderr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (UISettings::values.show_console) {
|
SetColorConsoleBackendEnabled(UISettings::values.show_console.GetValue());
|
||||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
|
||||||
} else {
|
|
||||||
Log::RemoveBackend(Log::ColorConsoleBackend::Name());
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} // namespace Debugger
|
} // namespace Debugger
|
||||||
|
|
|
@ -146,21 +146,6 @@ void GMainWindow::ShowTelemetryCallout() {
|
||||||
|
|
||||||
const int GMainWindow::max_recent_files_item;
|
const int GMainWindow::max_recent_files_item;
|
||||||
|
|
||||||
static void InitializeLogging() {
|
|
||||||
using namespace Common;
|
|
||||||
|
|
||||||
Log::Filter log_filter;
|
|
||||||
log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
|
||||||
Log::SetGlobalFilter(log_filter);
|
|
||||||
|
|
||||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
|
||||||
FileUtil::CreateFullPath(log_dir);
|
|
||||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + LOG_FILE));
|
|
||||||
#ifdef _WIN32
|
|
||||||
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static QString PrettyProductName() {
|
static QString PrettyProductName() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
|
// After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2
|
||||||
|
@ -186,7 +171,6 @@ static QString PrettyProductName() {
|
||||||
GMainWindow::GMainWindow(Core::System& system_)
|
GMainWindow::GMainWindow(Core::System& system_)
|
||||||
: ui{std::make_unique<Ui::MainWindow>()}, system{system_}, movie{Core::Movie::GetInstance()},
|
: ui{std::make_unique<Ui::MainWindow>()}, system{system_}, movie{Core::Movie::GetInstance()},
|
||||||
config{std::make_unique<Config>()}, emu_thread{nullptr} {
|
config{std::make_unique<Config>()}, emu_thread{nullptr} {
|
||||||
InitializeLogging();
|
|
||||||
Debugger::ToggleConsole();
|
Debugger::ToggleConsole();
|
||||||
Settings::LogSettings();
|
Settings::LogSettings();
|
||||||
|
|
||||||
|
@ -2869,6 +2853,7 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
Common::Log::Initialize();
|
||||||
Common::DetachedTasks detached_tasks;
|
Common::DetachedTasks detached_tasks;
|
||||||
MicroProfileOnThreadCreate("Frontend");
|
MicroProfileOnThreadCreate("Frontend");
|
||||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||||
|
@ -2893,7 +2878,8 @@ int main(int argc, char* argv[]) {
|
||||||
// generating shaders
|
// generating shaders
|
||||||
setlocale(LC_ALL, "C");
|
setlocale(LC_ALL, "C");
|
||||||
|
|
||||||
Core::System& system = Core::System::GetInstance();
|
Core::System::InitializeGlobalInstance();
|
||||||
|
auto& system{Core::System::GetInstance()};
|
||||||
GMainWindow main_window(system);
|
GMainWindow main_window(system);
|
||||||
|
|
||||||
// Register frontend applets
|
// Register frontend applets
|
||||||
|
|
|
@ -2,13 +2,10 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <condition_variable>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "common/common_paths.h"
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <share.h> // For _SH_DENYWR
|
#include <share.h> // For _SH_DENYWR
|
||||||
#include <windows.h> // For OutputDebugStringW
|
#include <windows.h> // For OutputDebugStringW
|
||||||
|
@ -16,6 +13,9 @@
|
||||||
#define _SH_DENYWR 0
|
#define _SH_DENYWR 0
|
||||||
#endif
|
#endif
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
|
#include "common/literals.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/thread.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/logging/text_formatter.h"
|
#include "common/logging/text_formatter.h"
|
||||||
|
@ -24,14 +24,176 @@
|
||||||
|
|
||||||
namespace Common::Log {
|
namespace Common::Log {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for logging backends.
|
||||||
|
*/
|
||||||
|
class Backend {
|
||||||
|
public:
|
||||||
|
virtual ~Backend() = default;
|
||||||
|
|
||||||
|
virtual void Write(const Entry& entry) = 0;
|
||||||
|
|
||||||
|
virtual void EnableForStacktrace() = 0;
|
||||||
|
|
||||||
|
virtual void Flush() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend that writes to stderr and with color
|
||||||
|
*/
|
||||||
|
class ColorConsoleBackend final : public Backend {
|
||||||
|
public:
|
||||||
|
explicit ColorConsoleBackend() = default;
|
||||||
|
|
||||||
|
~ColorConsoleBackend() override = default;
|
||||||
|
|
||||||
|
void Write(const Entry& entry) override {
|
||||||
|
if (enabled.load(std::memory_order_relaxed)) {
|
||||||
|
PrintColoredMessage(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flush() override {
|
||||||
|
// stderr shouldn't be buffered
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableForStacktrace() override {
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetEnabled(bool enabled_) {
|
||||||
|
enabled = enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic_bool enabled{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend that writes to a file passed into the constructor
|
||||||
|
*/
|
||||||
|
class FileBackend final : public Backend {
|
||||||
|
public:
|
||||||
|
explicit FileBackend(const std::string& filename) {
|
||||||
|
auto old_filename = filename;
|
||||||
|
old_filename += ".old.txt";
|
||||||
|
|
||||||
|
// Existence checks are done within the functions themselves.
|
||||||
|
// We don't particularly care if these succeed or not.
|
||||||
|
static_cast<void>(FileUtil::Delete(old_filename));
|
||||||
|
static_cast<void>(FileUtil::Rename(filename, old_filename));
|
||||||
|
|
||||||
|
// _SH_DENYWR allows read only access to the file for other programs.
|
||||||
|
// It is #defined to 0 on other platforms
|
||||||
|
file = std::make_unique<FileUtil::IOFile>(filename, "w", _SH_DENYWR);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FileBackend() override = default;
|
||||||
|
|
||||||
|
void Write(const Entry& entry) override {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
|
||||||
|
|
||||||
|
using namespace Common::Literals;
|
||||||
|
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
|
||||||
|
const auto write_limit = 100_MiB;
|
||||||
|
const bool write_limit_exceeded = bytes_written > write_limit;
|
||||||
|
if (entry.log_level >= Level::Error || write_limit_exceeded) {
|
||||||
|
if (write_limit_exceeded) {
|
||||||
|
// Stop writing after the write limit is exceeded.
|
||||||
|
// Don't close the file so we can print a stacktrace if necessary
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
file->Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flush() override {
|
||||||
|
file->Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableForStacktrace() override {
|
||||||
|
enabled = true;
|
||||||
|
bytes_written = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<FileUtil::IOFile> file;
|
||||||
|
bool enabled = true;
|
||||||
|
std::size_t bytes_written = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend that writes to Visual Studio's output window
|
||||||
|
*/
|
||||||
|
class DebuggerBackend final : public Backend {
|
||||||
|
public:
|
||||||
|
explicit DebuggerBackend() = default;
|
||||||
|
|
||||||
|
~DebuggerBackend() override = default;
|
||||||
|
|
||||||
|
void Write(const Entry& entry) override {
|
||||||
|
#ifdef _WIN32
|
||||||
|
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flush() override {}
|
||||||
|
|
||||||
|
void EnableForStacktrace() override {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef ANDROID
|
||||||
|
/**
|
||||||
|
* Backend that writes to the Android logcat
|
||||||
|
*/
|
||||||
|
class LogcatBackend : public Backend {
|
||||||
|
public:
|
||||||
|
explicit LogcatBackend() = default;
|
||||||
|
|
||||||
|
~LogcatBackend() override = default;
|
||||||
|
|
||||||
|
void Write(const Entry& entry) override {
|
||||||
|
PrintMessageToLogcat(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flush() override {}
|
||||||
|
|
||||||
|
void EnableForStacktrace() override {}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool initialization_in_progress_suppress_logging = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static state as a singleton.
|
* Static state as a singleton.
|
||||||
*/
|
*/
|
||||||
class Impl {
|
class Impl {
|
||||||
public:
|
public:
|
||||||
static Impl& Instance() {
|
static Impl& Instance() {
|
||||||
static Impl backend;
|
if (!instance) {
|
||||||
return backend;
|
abort();
|
||||||
|
}
|
||||||
|
return *instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Initialize() {
|
||||||
|
if (instance) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
initialization_in_progress_suppress_logging = true;
|
||||||
|
const auto& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
||||||
|
void(FileUtil::CreateDir(log_dir));
|
||||||
|
Filter filter;
|
||||||
|
filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||||
|
instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir + LOG_FILE, filter),
|
||||||
|
Deleter);
|
||||||
|
initialization_in_progress_suppress_logging = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Impl(const Impl&) = delete;
|
Impl(const Impl&) = delete;
|
||||||
|
@ -40,74 +202,54 @@ public:
|
||||||
Impl(Impl&&) = delete;
|
Impl(Impl&&) = delete;
|
||||||
Impl& operator=(Impl&&) = delete;
|
Impl& operator=(Impl&&) = delete;
|
||||||
|
|
||||||
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
|
||||||
const char* function, std::string message) {
|
|
||||||
message_queue.Push(
|
|
||||||
CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
|
||||||
std::lock_guard lock{writing_mutex};
|
|
||||||
backends.push_back(std::move(backend));
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveBackend(std::string_view backend_name) {
|
|
||||||
std::lock_guard lock{writing_mutex};
|
|
||||||
|
|
||||||
std::erase_if(backends, [&backend_name](const auto& backend) {
|
|
||||||
return backend_name == backend->GetName();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Filter& GetGlobalFilter() const {
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetGlobalFilter(const Filter& f) {
|
void SetGlobalFilter(const Filter& f) {
|
||||||
filter = f;
|
filter = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
Backend* GetBackend(std::string_view backend_name) {
|
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||||
const auto it =
|
color_console_backend.SetEnabled(enabled);
|
||||||
std::find_if(backends.begin(), backends.end(),
|
}
|
||||||
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
|
|
||||||
if (it == backends.end())
|
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||||
return nullptr;
|
const char* function, std::string message) {
|
||||||
return it->get();
|
if (!filter.CheckMessage(log_class, log_level))
|
||||||
|
return;
|
||||||
|
const Entry& entry =
|
||||||
|
CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
|
||||||
|
message_queue.Push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Impl() {
|
Impl(const std::string& file_backend_filename, const Filter& filter_)
|
||||||
backend_thread = std::thread([&] {
|
: filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] {
|
||||||
|
Common::SetCurrentThreadName("citra:Log");
|
||||||
Entry entry;
|
Entry entry;
|
||||||
auto write_logs = [&](Entry& e) {
|
const auto write_logs = [this, &entry]() {
|
||||||
std::lock_guard lock{writing_mutex};
|
ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
|
||||||
for (const auto& backend : backends) {
|
|
||||||
backend->Write(e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
while (true) {
|
while (true) {
|
||||||
entry = message_queue.PopWait();
|
entry = message_queue.PopWait();
|
||||||
if (entry.final_entry) {
|
if (entry.final_entry) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
write_logs(entry);
|
write_logs();
|
||||||
}
|
}
|
||||||
|
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
|
||||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
|
// case where a system is repeatedly spamming logs even on close.
|
||||||
// where a system is repeatedly spamming logs even on close.
|
int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
|
||||||
constexpr int MAX_LOGS_TO_WRITE = 100;
|
while (max_logs_to_write-- && message_queue.Pop(entry)) {
|
||||||
int logs_written = 0;
|
write_logs();
|
||||||
while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
|
|
||||||
write_logs(entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
})} {}
|
||||||
|
|
||||||
~Impl() {
|
~Impl() {
|
||||||
Entry entry;
|
StopBackendThread();
|
||||||
entry.final_entry = true;
|
}
|
||||||
message_queue.Push(entry);
|
|
||||||
|
void StopBackendThread() {
|
||||||
|
Entry stop_entry{};
|
||||||
|
stop_entry.final_entry = true;
|
||||||
|
message_queue.Push(stop_entry);
|
||||||
backend_thread.join();
|
backend_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,102 +271,51 @@ private:
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mutex writing_mutex;
|
void ForEachBackend(auto lambda) {
|
||||||
std::thread backend_thread;
|
lambda(static_cast<Backend&>(debugger_backend));
|
||||||
std::vector<std::unique_ptr<Backend>> backends;
|
lambda(static_cast<Backend&>(color_console_backend));
|
||||||
Common::MPSCQueue<Log::Entry> message_queue;
|
lambda(static_cast<Backend&>(file_backend));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Deleter(Impl* ptr) {
|
||||||
|
delete ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
|
||||||
|
|
||||||
Filter filter;
|
Filter filter;
|
||||||
|
DebuggerBackend debugger_backend{};
|
||||||
|
ColorConsoleBackend color_console_backend{};
|
||||||
|
FileBackend file_backend;
|
||||||
|
|
||||||
|
std::thread backend_thread;
|
||||||
|
MPSCQueue<Entry> message_queue{};
|
||||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||||
};
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
ConsoleBackend::~ConsoleBackend() = default;
|
void Initialize() {
|
||||||
|
Impl::Initialize();
|
||||||
void ConsoleBackend::Write(const Entry& entry) {
|
|
||||||
PrintMessage(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorConsoleBackend::~ColorConsoleBackend() = default;
|
void DisableLoggingInTests() {
|
||||||
|
initialization_in_progress_suppress_logging = true;
|
||||||
void ColorConsoleBackend::Write(const Entry& entry) {
|
|
||||||
PrintColoredMessage(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogcatBackend::~LogcatBackend() = default;
|
|
||||||
|
|
||||||
void LogcatBackend::Write(const Entry& entry) {
|
|
||||||
PrintMessageToLogcat(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileBackend::FileBackend(const std::string& filename) {
|
|
||||||
const auto old_filename = filename + ".old.txt";
|
|
||||||
|
|
||||||
if (FileUtil::Exists(old_filename)) {
|
|
||||||
FileUtil::Delete(old_filename);
|
|
||||||
}
|
|
||||||
if (FileUtil::Exists(filename)) {
|
|
||||||
FileUtil::Rename(filename, old_filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
// _SH_DENYWR allows read only access to the file for other programs.
|
|
||||||
// It is #defined to 0 on other platforms
|
|
||||||
file = std::make_unique<FileUtil::IOFile>(filename, "w", _SH_DENYWR);
|
|
||||||
}
|
|
||||||
|
|
||||||
FileBackend::~FileBackend() = default;
|
|
||||||
|
|
||||||
void FileBackend::Write(const Entry& entry) {
|
|
||||||
if (!file->IsOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
|
|
||||||
constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
|
|
||||||
|
|
||||||
// Close the file after the write limit is exceeded.
|
|
||||||
if (bytes_written > MAX_BYTES_WRITTEN) {
|
|
||||||
file->Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
|
|
||||||
if (entry.log_level >= Level::Error) {
|
|
||||||
file->Flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DebuggerBackend::~DebuggerBackend() = default;
|
|
||||||
|
|
||||||
void DebuggerBackend::Write(const Entry& entry) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetGlobalFilter(const Filter& filter) {
|
void SetGlobalFilter(const Filter& filter) {
|
||||||
Impl::Instance().SetGlobalFilter(filter);
|
Impl::Instance().SetGlobalFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||||
Impl::Instance().AddBackend(std::move(backend));
|
Impl::Instance().SetColorConsoleBackendEnabled(enabled);
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveBackend(std::string_view backend_name) {
|
|
||||||
Impl::Instance().RemoveBackend(backend_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Backend* GetBackend(std::string_view backend_name) {
|
|
||||||
return Impl::Instance().GetBackend(backend_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||||
unsigned int line_num, const char* function, const char* format,
|
unsigned int line_num, const char* function, const char* format,
|
||||||
const fmt::format_args& args) {
|
const fmt::format_args& args) {
|
||||||
auto& instance = Impl::Instance();
|
if (!initialization_in_progress_suppress_logging) {
|
||||||
const auto& filter = instance.GetGlobalFilter();
|
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
|
||||||
if (!filter.CheckMessage(log_class, log_level))
|
|
||||||
return;
|
|
||||||
|
|
||||||
instance.PushEntry(log_class, log_level, filename, line_num, function,
|
|
||||||
fmt::vformat(format, args));
|
fmt::vformat(format, args));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // namespace Common::Log
|
} // namespace Common::Log
|
||||||
|
|
|
@ -4,136 +4,22 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <filesystem>
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include "common/logging/filter.h"
|
#include "common/logging/filter.h"
|
||||||
#include "common/logging/log.h"
|
|
||||||
|
|
||||||
namespace FileUtil {
|
|
||||||
class IOFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Common::Log {
|
namespace Common::Log {
|
||||||
|
|
||||||
/**
|
class Filter;
|
||||||
* Interface for logging backends. As loggers can be created and removed at runtime, this can be
|
|
||||||
* used by a frontend for adding a custom logging backend as needed
|
|
||||||
*/
|
|
||||||
class Backend {
|
|
||||||
public:
|
|
||||||
virtual ~Backend() = default;
|
|
||||||
|
|
||||||
virtual void SetFilter(const Filter& new_filter) {
|
/// Initializes the logging system. This should be the first thing called in main.
|
||||||
filter = new_filter;
|
void Initialize();
|
||||||
}
|
|
||||||
virtual const char* GetName() const = 0;
|
|
||||||
virtual void Write(const Entry& entry) = 0;
|
|
||||||
|
|
||||||
private:
|
void DisableLoggingInTests();
|
||||||
Filter filter;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backend that writes to stderr without any color commands
|
* The global filter will prevent any messages from even being processed if they are filtered.
|
||||||
*/
|
|
||||||
class ConsoleBackend : public Backend {
|
|
||||||
public:
|
|
||||||
~ConsoleBackend() override;
|
|
||||||
|
|
||||||
static const char* Name() {
|
|
||||||
return "console";
|
|
||||||
}
|
|
||||||
const char* GetName() const override {
|
|
||||||
return Name();
|
|
||||||
}
|
|
||||||
void Write(const Entry& entry) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backend that writes to stderr and with color
|
|
||||||
*/
|
|
||||||
class ColorConsoleBackend : public Backend {
|
|
||||||
public:
|
|
||||||
~ColorConsoleBackend() override;
|
|
||||||
|
|
||||||
static const char* Name() {
|
|
||||||
return "color_console";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetName() const override {
|
|
||||||
return Name();
|
|
||||||
}
|
|
||||||
void Write(const Entry& entry) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backend that writes to the Android logcat
|
|
||||||
*/
|
|
||||||
class LogcatBackend : public Backend {
|
|
||||||
public:
|
|
||||||
~LogcatBackend() override;
|
|
||||||
|
|
||||||
static const char* Name() {
|
|
||||||
return "logcat";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetName() const override {
|
|
||||||
return Name();
|
|
||||||
}
|
|
||||||
void Write(const Entry& entry) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backend that writes to a file passed into the constructor
|
|
||||||
*/
|
|
||||||
class FileBackend : public Backend {
|
|
||||||
public:
|
|
||||||
~FileBackend() override;
|
|
||||||
|
|
||||||
explicit FileBackend(const std::string& filename);
|
|
||||||
|
|
||||||
static const char* Name() {
|
|
||||||
return "file";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* GetName() const override {
|
|
||||||
return Name();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Write(const Entry& entry) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<FileUtil::IOFile> file;
|
|
||||||
std::size_t bytes_written = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backend that writes to Visual Studio's output window
|
|
||||||
*/
|
|
||||||
class DebuggerBackend : public Backend {
|
|
||||||
public:
|
|
||||||
~DebuggerBackend() override;
|
|
||||||
|
|
||||||
static const char* Name() {
|
|
||||||
return "debugger";
|
|
||||||
}
|
|
||||||
const char* GetName() const override {
|
|
||||||
return Name();
|
|
||||||
}
|
|
||||||
void Write(const Entry& entry) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
void AddBackend(std::unique_ptr<Backend> backend);
|
|
||||||
|
|
||||||
void RemoveBackend(std::string_view backend_name);
|
|
||||||
|
|
||||||
Backend* GetBackend(std::string_view backend_name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The global filter will prevent any messages from even being processed if they are filtered. Each
|
|
||||||
* backend can have a filter, but if the level is lower than the global filter, the backend will
|
|
||||||
* never get the message
|
|
||||||
*/
|
*/
|
||||||
void SetGlobalFilter(const Filter& filter);
|
void SetGlobalFilter(const Filter& filter);
|
||||||
|
|
||||||
|
void SetColorConsoleBackendEnabled(bool enabled);
|
||||||
} // namespace Common::Log
|
} // namespace Common::Log
|
||||||
|
|
|
@ -204,4 +204,11 @@ bool Filter::CheckMessage(Class log_class, Level level) const {
|
||||||
return static_cast<u8>(level) >=
|
return static_cast<u8>(level) >=
|
||||||
static_cast<u8>(class_levels[static_cast<std::size_t>(log_class)]);
|
static_cast<u8>(class_levels[static_cast<std::size_t>(log_class)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Filter::IsDebug() const {
|
||||||
|
return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) {
|
||||||
|
return static_cast<u8>(l) <= static_cast<u8>(Level::Debug);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common::Log
|
} // namespace Common::Log
|
||||||
|
|
|
@ -56,6 +56,9 @@ public:
|
||||||
/// Matches class/level combination against the filter, returning true if it passed.
|
/// Matches class/level combination against the filter, returning true if it passed.
|
||||||
bool CheckMessage(Class log_class, Level level) const;
|
bool CheckMessage(Class log_class, Level level) const;
|
||||||
|
|
||||||
|
/// Returns true if any logging classes are set to debug
|
||||||
|
bool IsDebug() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<Level, static_cast<std::size_t>(Class::Count)> class_levels;
|
std::array<Level, static_cast<std::size_t>(Class::Count)> class_levels;
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,8 +51,6 @@
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
/*static*/ System System::s_instance;
|
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
Core::System& Global() {
|
Core::System& Global() {
|
||||||
return System::GetInstance();
|
return System::GetInstance();
|
||||||
|
@ -70,6 +68,13 @@ Core::Timing& Global() {
|
||||||
|
|
||||||
System::~System() = default;
|
System::~System() = default;
|
||||||
|
|
||||||
|
void System::InitializeGlobalInstance() {
|
||||||
|
if (s_instance) {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
s_instance = std::unique_ptr<System>(new System);
|
||||||
|
}
|
||||||
|
|
||||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||||
status = ResultStatus::Success;
|
status = ResultStatus::Success;
|
||||||
if (!IsPoweredOn()) {
|
if (!IsPoweredOn()) {
|
||||||
|
|
|
@ -74,8 +74,13 @@ public:
|
||||||
* @returns Reference to the instance of the System singleton class.
|
* @returns Reference to the instance of the System singleton class.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] static System& GetInstance() {
|
[[nodiscard]] static System& GetInstance() {
|
||||||
return s_instance;
|
if (!s_instance) {
|
||||||
|
std::abort();
|
||||||
}
|
}
|
||||||
|
return *s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitializeGlobalInstance();
|
||||||
|
|
||||||
/// Enumeration representing the return values of the System Initialize and Load process.
|
/// Enumeration representing the return values of the System Initialize and Load process.
|
||||||
enum class ResultStatus : u32 {
|
enum class ResultStatus : u32 {
|
||||||
|
@ -392,7 +397,7 @@ private:
|
||||||
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
|
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static System s_instance;
|
inline static std::unique_ptr<System> s_instance;
|
||||||
|
|
||||||
std::atomic_bool is_powered_on{};
|
std::atomic_bool is_powered_on{};
|
||||||
|
|
||||||
|
|
|
@ -150,17 +150,8 @@ static void SaveBanList(const Network::Room::BanList& ban_list, const std::strin
|
||||||
}
|
}
|
||||||
|
|
||||||
static void InitializeLogging(const std::string& log_file) {
|
static void InitializeLogging(const std::string& log_file) {
|
||||||
using namespace Common;
|
Common::Log::Initialize();
|
||||||
|
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||||
Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
|
|
||||||
|
|
||||||
const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
|
|
||||||
FileUtil::CreateFullPath(log_dir);
|
|
||||||
Log::AddBackend(std::make_unique<Log::FileBackend>(log_dir + log_file));
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
Log::AddBackend(std::make_unique<Log::DebuggerBackend>());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Application entry point
|
/// Application entry point
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include "common/logging/backend.h"
|
||||||
#include "common/param_package.h"
|
#include "common/param_package.h"
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
TEST_CASE("ParamPackage", "[common]") {
|
TEST_CASE("ParamPackage", "[common]") {
|
||||||
|
Common::Log::DisableLoggingInTests();
|
||||||
ParamPackage original{
|
ParamPackage original{
|
||||||
{"abc", "xyz"},
|
{"abc", "xyz"},
|
||||||
{"def", "42"},
|
{"def", "42"},
|
||||||
|
|
Reference in New Issue