|
|
@ -1,4 +1,4 @@ |
|
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|
|
|
|
|
|
|
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
|
|
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
|
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
|
|
@ -7,6 +7,7 @@ |
|
|
#include <atomic>
|
|
|
#include <atomic>
|
|
|
#include <chrono>
|
|
|
#include <chrono>
|
|
|
#include <climits>
|
|
|
#include <climits>
|
|
|
|
|
|
#include <cstdlib>
|
|
|
#include <regex>
|
|
|
#include <regex>
|
|
|
#include <thread>
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
|
@ -41,7 +42,7 @@ namespace { |
|
|
|
|
|
|
|
|
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
|
|
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
|
|
/// do not be fooled this isn't generating new strings on .rodata :)
|
|
|
/// do not be fooled this isn't generating new strings on .rodata :)
|
|
|
constexpr const char* TrimSourcePath(std::string_view source) { |
|
|
|
|
|
|
|
|
constexpr const char* TrimSourcePath(std::string_view source) noexcept { |
|
|
const auto rfind = [source](const std::string_view match) { |
|
|
const auto rfind = [source](const std::string_view match) { |
|
|
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size()); |
|
|
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size()); |
|
|
}; |
|
|
}; |
|
|
@ -51,31 +52,31 @@ constexpr const char* TrimSourcePath(std::string_view source) { |
|
|
|
|
|
|
|
|
/// @brief Interface for logging backends.
|
|
|
/// @brief Interface for logging backends.
|
|
|
struct Backend { |
|
|
struct Backend { |
|
|
virtual ~Backend() = default; |
|
|
|
|
|
virtual void Write(const Entry& entry) = 0; |
|
|
|
|
|
virtual void EnableForStacktrace() = 0; |
|
|
|
|
|
virtual void Flush() = 0; |
|
|
|
|
|
|
|
|
virtual ~Backend() noexcept = default; |
|
|
|
|
|
virtual void Write(const Entry& entry) noexcept = 0; |
|
|
|
|
|
virtual void EnableForStacktrace() noexcept= 0; |
|
|
|
|
|
virtual void Flush() noexcept = 0; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/// @brief Backend that writes to stderr and with color
|
|
|
/// @brief Backend that writes to stderr and with color
|
|
|
struct ColorConsoleBackend final : public Backend { |
|
|
struct ColorConsoleBackend final : public Backend { |
|
|
explicit ColorConsoleBackend() = default; |
|
|
|
|
|
~ColorConsoleBackend() override = default; |
|
|
|
|
|
|
|
|
explicit ColorConsoleBackend() noexcept = default; |
|
|
|
|
|
~ColorConsoleBackend() noexcept override = default; |
|
|
|
|
|
|
|
|
void Write(const Entry& entry) override { |
|
|
|
|
|
|
|
|
void Write(const Entry& entry) noexcept override { |
|
|
if (enabled.load(std::memory_order_relaxed)) |
|
|
if (enabled.load(std::memory_order_relaxed)) |
|
|
PrintColoredMessage(entry); |
|
|
PrintColoredMessage(entry); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void Flush() override { |
|
|
|
|
|
|
|
|
void Flush() noexcept override { |
|
|
// stderr shouldn't be buffered
|
|
|
// stderr shouldn't be buffered
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void EnableForStacktrace() override { |
|
|
|
|
|
|
|
|
void EnableForStacktrace() noexcept override { |
|
|
enabled = true; |
|
|
enabled = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void SetEnabled(bool enabled_) { |
|
|
|
|
|
|
|
|
void SetEnabled(bool enabled_) noexcept { |
|
|
enabled = enabled_; |
|
|
enabled = enabled_; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -83,9 +84,10 @@ private: |
|
|
std::atomic_bool enabled{false}; |
|
|
std::atomic_bool enabled{false}; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
#ifndef __OPENORBIS__
|
|
|
/// @brief Backend that writes to a file passed into the constructor
|
|
|
/// @brief Backend that writes to a file passed into the constructor
|
|
|
struct FileBackend final : public Backend { |
|
|
struct FileBackend final : public Backend { |
|
|
explicit FileBackend(const std::filesystem::path& filename) { |
|
|
|
|
|
|
|
|
explicit FileBackend(const std::filesystem::path& filename) noexcept { |
|
|
auto old_filename = filename; |
|
|
auto old_filename = filename; |
|
|
old_filename += ".old.txt"; |
|
|
old_filename += ".old.txt"; |
|
|
|
|
|
|
|
|
@ -97,9 +99,9 @@ struct FileBackend final : public Backend { |
|
|
file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); |
|
|
file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
~FileBackend() override = default; |
|
|
|
|
|
|
|
|
~FileBackend() noexcept override = default; |
|
|
|
|
|
|
|
|
void Write(const Entry& entry) override { |
|
|
|
|
|
|
|
|
void Write(const Entry& entry) noexcept override { |
|
|
if (!enabled) |
|
|
if (!enabled) |
|
|
return; |
|
|
return; |
|
|
|
|
|
|
|
|
@ -147,11 +149,11 @@ struct FileBackend final : public Backend { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void Flush() override { |
|
|
|
|
|
|
|
|
void Flush() noexcept override { |
|
|
file->Flush(); |
|
|
file->Flush(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void EnableForStacktrace() override { |
|
|
|
|
|
|
|
|
void EnableForStacktrace() noexcept override { |
|
|
enabled = true; |
|
|
enabled = true; |
|
|
bytes_written = 0; |
|
|
bytes_written = 0; |
|
|
} |
|
|
} |
|
|
@ -161,29 +163,30 @@ private: |
|
|
std::size_t bytes_written = 0; |
|
|
std::size_t bytes_written = 0; |
|
|
bool enabled = true; |
|
|
bool enabled = true; |
|
|
}; |
|
|
}; |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
#ifdef _WIN32
|
|
|
/// @brief Backend that writes to Visual Studio's output window
|
|
|
/// @brief Backend that writes to Visual Studio's output window
|
|
|
struct DebuggerBackend final : public Backend { |
|
|
struct DebuggerBackend final : public Backend { |
|
|
explicit DebuggerBackend() = default; |
|
|
|
|
|
~DebuggerBackend() override = default; |
|
|
|
|
|
void Write(const Entry& entry) override { |
|
|
|
|
|
|
|
|
explicit DebuggerBackend() noexcept = default; |
|
|
|
|
|
~DebuggerBackend() noexcept override = default; |
|
|
|
|
|
void Write(const Entry& entry) noexcept override { |
|
|
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); |
|
|
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); |
|
|
} |
|
|
} |
|
|
void Flush() override {} |
|
|
|
|
|
void EnableForStacktrace() override {} |
|
|
|
|
|
|
|
|
void Flush() noexcept override {} |
|
|
|
|
|
void EnableForStacktrace() noexcept override {} |
|
|
}; |
|
|
}; |
|
|
#endif
|
|
|
#endif
|
|
|
#ifdef ANDROID
|
|
|
#ifdef ANDROID
|
|
|
/// @brief Backend that writes to the Android logcat
|
|
|
/// @brief Backend that writes to the Android logcat
|
|
|
struct LogcatBackend : public Backend { |
|
|
struct LogcatBackend : public Backend { |
|
|
explicit LogcatBackend() = default; |
|
|
|
|
|
~LogcatBackend() override = default; |
|
|
|
|
|
void Write(const Entry& entry) override { |
|
|
|
|
|
|
|
|
explicit LogcatBackend() noexcept = default; |
|
|
|
|
|
~LogcatBackend() noexcept override = default; |
|
|
|
|
|
void Write(const Entry& entry) noexcept override { |
|
|
PrintMessageToLogcat(entry); |
|
|
PrintMessageToLogcat(entry); |
|
|
} |
|
|
} |
|
|
void Flush() override {} |
|
|
|
|
|
void EnableForStacktrace() override {} |
|
|
|
|
|
|
|
|
void Flush() noexcept override {} |
|
|
|
|
|
void EnableForStacktrace() noexcept override {} |
|
|
}; |
|
|
}; |
|
|
#endif
|
|
|
#endif
|
|
|
|
|
|
|
|
|
@ -192,13 +195,13 @@ bool initialization_in_progress_suppress_logging = true; |
|
|
/// @brief Static state as a singleton.
|
|
|
/// @brief Static state as a singleton.
|
|
|
class Impl { |
|
|
class Impl { |
|
|
public: |
|
|
public: |
|
|
static Impl& Instance() { |
|
|
|
|
|
|
|
|
static Impl& Instance() noexcept { |
|
|
if (!instance) |
|
|
if (!instance) |
|
|
throw std::runtime_error("Using Logging instance before its initialization"); |
|
|
|
|
|
|
|
|
std::abort(); |
|
|
return *instance; |
|
|
return *instance; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static void Initialize() { |
|
|
|
|
|
|
|
|
static void Initialize() noexcept { |
|
|
if (instance) { |
|
|
if (instance) { |
|
|
LOG_WARNING(Log, "Reinitializing logging backend"); |
|
|
LOG_WARNING(Log, "Reinitializing logging backend"); |
|
|
return; |
|
|
return; |
|
|
@ -212,25 +215,25 @@ public: |
|
|
initialization_in_progress_suppress_logging = false; |
|
|
initialization_in_progress_suppress_logging = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static void Start() { |
|
|
|
|
|
|
|
|
static void Start() noexcept { |
|
|
instance->StartBackendThread(); |
|
|
instance->StartBackendThread(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static void Stop() { |
|
|
|
|
|
|
|
|
static void Stop() noexcept { |
|
|
instance->StopBackendThread(); |
|
|
instance->StopBackendThread(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Impl(const Impl&) = delete; |
|
|
|
|
|
Impl& operator=(const Impl&) = delete; |
|
|
|
|
|
|
|
|
Impl(const Impl&) noexcept = delete; |
|
|
|
|
|
Impl& operator=(const Impl&) noexcept = delete; |
|
|
|
|
|
|
|
|
Impl(Impl&&) = delete; |
|
|
|
|
|
Impl& operator=(Impl&&) = delete; |
|
|
|
|
|
|
|
|
Impl(Impl&&) noexcept = delete; |
|
|
|
|
|
Impl& operator=(Impl&&) noexcept = delete; |
|
|
|
|
|
|
|
|
void SetGlobalFilter(const Filter& f) { |
|
|
|
|
|
|
|
|
void SetGlobalFilter(const Filter& f) noexcept { |
|
|
filter = f; |
|
|
filter = f; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void SetColorConsoleBackendEnabled(bool enabled) { |
|
|
|
|
|
|
|
|
void SetColorConsoleBackendEnabled(bool enabled) noexcept { |
|
|
color_console_backend.SetEnabled(enabled); |
|
|
color_console_backend.SetEnabled(enabled); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -238,19 +241,21 @@ public: |
|
|
return filter.CheckMessage(log_class, log_level); |
|
|
return filter.CheckMessage(log_class, log_level); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, |
|
|
|
|
|
const char* function, std::string&& message) noexcept { |
|
|
|
|
|
message_queue.EmplaceWait( |
|
|
|
|
|
CreateEntry(log_class, log_level, TrimSourcePath(filename), line_num, function, std::move(message))); |
|
|
|
|
|
|
|
|
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, std::string&& message) noexcept { |
|
|
|
|
|
message_queue.EmplaceWait(CreateEntry(log_class, log_level, TrimSourcePath(filename), line_num, function, std::move(message))); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private: |
|
|
private: |
|
|
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) |
|
|
|
|
|
: filter{filter_}, file_backend{file_backend_filename} {} |
|
|
|
|
|
|
|
|
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) noexcept : |
|
|
|
|
|
filter{filter_} |
|
|
|
|
|
#ifndef __OPENORBIS__
|
|
|
|
|
|
, file_backend{file_backend_filename} |
|
|
|
|
|
#endif
|
|
|
|
|
|
{} |
|
|
|
|
|
|
|
|
~Impl() = default; |
|
|
|
|
|
|
|
|
~Impl() noexcept = default; |
|
|
|
|
|
|
|
|
void StartBackendThread() { |
|
|
|
|
|
|
|
|
void StartBackendThread() noexcept { |
|
|
backend_thread = std::jthread([this](std::stop_token stop_token) { |
|
|
backend_thread = std::jthread([this](std::stop_token stop_token) { |
|
|
Common::SetCurrentThreadName("Logger"); |
|
|
Common::SetCurrentThreadName("Logger"); |
|
|
Entry entry; |
|
|
Entry entry; |
|
|
@ -271,15 +276,14 @@ private: |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void StopBackendThread() { |
|
|
|
|
|
|
|
|
void StopBackendThread() noexcept { |
|
|
backend_thread.request_stop(); |
|
|
backend_thread.request_stop(); |
|
|
if (backend_thread.joinable()) |
|
|
if (backend_thread.joinable()) |
|
|
backend_thread.join(); |
|
|
backend_thread.join(); |
|
|
ForEachBackend([](Backend& backend) { backend.Flush(); }); |
|
|
ForEachBackend([](Backend& backend) { backend.Flush(); }); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, |
|
|
|
|
|
const char* function, std::string&& message) const { |
|
|
|
|
|
|
|
|
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, const char* function, std::string&& message) const noexcept { |
|
|
using std::chrono::duration_cast; |
|
|
using std::chrono::duration_cast; |
|
|
using std::chrono::microseconds; |
|
|
using std::chrono::microseconds; |
|
|
using std::chrono::steady_clock; |
|
|
using std::chrono::steady_clock; |
|
|
@ -294,9 +298,11 @@ private: |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void ForEachBackend(auto lambda) { |
|
|
|
|
|
|
|
|
void ForEachBackend(auto lambda) noexcept { |
|
|
lambda(static_cast<Backend&>(color_console_backend)); |
|
|
lambda(static_cast<Backend&>(color_console_backend)); |
|
|
|
|
|
#ifndef __OPENORBIS__
|
|
|
lambda(static_cast<Backend&>(file_backend)); |
|
|
lambda(static_cast<Backend&>(file_backend)); |
|
|
|
|
|
#endif
|
|
|
#ifdef _WIN32
|
|
|
#ifdef _WIN32
|
|
|
lambda(static_cast<Backend&>(debugger_backend)); |
|
|
lambda(static_cast<Backend&>(debugger_backend)); |
|
|
#endif
|
|
|
#endif
|
|
|
@ -305,7 +311,7 @@ private: |
|
|
#endif
|
|
|
#endif
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static void Deleter(Impl* ptr) { |
|
|
|
|
|
|
|
|
static void Deleter(Impl* ptr) noexcept { |
|
|
delete ptr; |
|
|
delete ptr; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -313,7 +319,9 @@ private: |
|
|
|
|
|
|
|
|
Filter filter; |
|
|
Filter filter; |
|
|
ColorConsoleBackend color_console_backend{}; |
|
|
ColorConsoleBackend color_console_backend{}; |
|
|
|
|
|
#ifndef __OPENORBIS__
|
|
|
FileBackend file_backend; |
|
|
FileBackend file_backend; |
|
|
|
|
|
#endif
|
|
|
#ifdef _WIN32
|
|
|
#ifdef _WIN32
|
|
|
DebuggerBackend debugger_backend{}; |
|
|
DebuggerBackend debugger_backend{}; |
|
|
#endif
|
|
|
#endif
|
|
|
|