eden-miror/src/common/logging.h
lizzie 395613b01f
Some checks are pending
tx-src / sources (push) Waiting to run
Check Strings / check-strings (push) Waiting to run
[common/logging] Simplify logging logic and fix issues when logging before system is created (#3688)
- our logging code was bigger than spdlog itself, why???? just keep it simple
- fix issues when logging before logging system is even started
- removes the "initialized logging twice" issue
- removes uneeded indirection in file logging
- uses direct formatting instead of jumping hoopla-around the fmt::format() ressult
- code duplication and dead code removal as usual

I did explore dup2() but I think it's not worth the hassle
I did try `fwopen()` but it's better if things are just kept as-is.

there is a lot of noise because I removed a bunch of redundant files on logging and just put everything in one file

now normally this wouldn't be a good idea, however consider: the complexity of logging; it's less than 500 lines... does it really need a whole subsystem?!?!?! ITS JUST LOGGING

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3688
Reviewed-by: crueter <crueter@eden-emu.dev>
Reviewed-by: DraVee <chimera@dravee.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-03-12 18:29:15 +01:00

164 lines
7 KiB
C++

// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include <algorithm>
#include <type_traits>
#include <fmt/ranges.h>
#include "common/swap.h"
// adapted from https://github.com/fmtlib/fmt/issues/2704
// a generic formatter for enum classes
#if FMT_VERSION >= 80100
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
: formatter<std::underlying_type_t<T>> {
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
return fmt::formatter<std::underlying_type_t<T>>::format(
static_cast<std::underlying_type_t<T>>(value), ctx);
}
};
#endif
template <typename T, typename U>
struct fmt::formatter<SwapStructT<T, U>> {
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const SwapStructT<T, U>& reg, FormatContext& ctx) const {
return fmt::format_to(ctx.out(), "{}", T(reg));
}
};
#ifdef _DEBUG
#define LOG_TRACE(log_class, ...) \
Common::Log::FmtLogMessage(Common::Log::Class::log_class, Common::Log::Level::Trace, \
__FILE__, __LINE__, __func__, __VA_ARGS__)
#else
#define LOG_TRACE(log_class, fmt, ...) (void(0))
#endif
#define LOG_DEBUG(log_class, ...) \
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Debug, \
__FILE__, __LINE__, __func__, __VA_ARGS__)
#define LOG_INFO(log_class, ...) \
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Info, \
__FILE__, __LINE__, __func__, __VA_ARGS__)
#define LOG_WARNING(log_class, ...) \
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Warning, \
__FILE__, __LINE__, __func__, __VA_ARGS__)
#define LOG_ERROR(log_class, ...) \
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Error, \
__FILE__, __LINE__, __func__, __VA_ARGS__)
#define LOG_CRITICAL(log_class, ...) \
::Common::Log::FmtLogMessage(::Common::Log::Class::log_class, ::Common::Log::Level::Critical, \
__FILE__, __LINE__, __func__, __VA_ARGS__)
namespace Common::Log {
/// Specifies the severity or level of detail of the log message.
enum class Level : u8 {
Trace, ///< Extremely detailed and repetitive debugging information that is likely to pollute logs.
Debug, ///< Less detailed debugging information.
Info, ///< Status information from important points during execution.
Warning, ///< Minor or potential problems found during execution of a task.
Error, ///< Major problems found during execution of a task that prevent it from being completed.
Critical, ///< Major problems during execution that threaten the stability of the entire application.
Count ///< Total number of logging levels
};
/// Specifies the sub-system that generated the log message.
enum class Class : u8 {
#define SUB(A, B) A##_##B,
#define CLS(A) A,
#include "log_classes.inc"
#undef SUB
#undef CLS
Count,
};
/// Logs a message to the global logger, using fmt
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args);
template <typename... Args>
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::format_string<Args...> format, const Args&... args) {
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, fmt::make_format_args(args...));
}
/// Implements a log message filter which allows different log classes to have different minimum
/// severity levels. The filter can be changed at runtime and can be parsed from a string to allow
/// editing via the interface or loading from a configuration file.
struct Filter {
/// Initializes the filter with all classes having `default_level` as the minimum level.
explicit Filter(Level level = Level::Info) {
class_levels.fill(level);
}
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`.
void SetClassLevel(Class log_class, Level level) {
class_levels[std::size_t(log_class)] = level;
}
/// Parses a filter string and applies it to this filter.
/// A filter string consists of a space-separated list of filter rules, each of the format
/// `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods.
/// `*` is allowed as a class name and will reset all filters to the specified level. `<level>`
/// a severity level name which will be set as the minimum logging level of the matched classes.
/// Rules are applied left to right, with each rule overriding previous ones in the sequence.
/// A few examples of filter rules:
/// - `*:Info` -- Resets the level of all classes to Info.
/// - `Service:Info` -- Sets the level of Service to Info.
/// - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace.
void ParseFilterString(std::string_view filter_view);
/// Matches class/level combination against the filter, returning true if it passed.
[[nodiscard]] bool CheckMessage(Class log_class, Level level) const {
return u8(level) >= u8(class_levels[std::size_t(log_class)]);
}
/// Returns true if any logging classes are set to debug
[[nodiscard]] bool IsDebug() const {
return std::any_of(class_levels.begin(), class_levels.end(), [](const Level& l) {
return u8(l) <= u8(Level::Debug);
});
}
std::array<Level, std::size_t(Class::Count)> class_levels;
};
/// Initializes the logging system. This should be the first thing called in main.
void Initialize();
void Start();
/// Explicitly stops the logger thread and flushes the buffers
void Stop();
/// The global filter will prevent any messages from even being processed if they are filtered.
void SetGlobalFilter(const Filter& filter);
void SetColorConsoleBackendEnabled(bool enabled);
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
/// formatting on different frontends, as well as facilitating filtering and aggregation.
struct Entry {
std::string message;
std::chrono::microseconds timestamp;
Class log_class{};
Level log_level{};
const char* filename = nullptr;
const char* function = nullptr;
unsigned int line_num = 0;
};
/// Formats a log entry into the provided text buffer.
std::string FormatLogMessage(const Entry& entry) noexcept;
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
void PrintColoredMessage(const Entry& entry) noexcept;
/// Formats and prints a log entry to the android logcat.
void PrintMessageToLogcat(const Entry& entry) noexcept;
} // namespace Common::Log