#pragma once #if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED) #define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1 #include #include #include #include #include #include "../internal/common.hpp" #include "../util/annot.hpp" namespace mijin { struct LogLevel { const char* name; int value; explicit operator int() const MIJIN_NOEXCEPT { return value; } auto operator<=>(const LogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; } }; struct LogChannel { const char* name; }; struct LogMessage { const char* text; const LogChannel* channel; const LogLevel* level; std::source_location sourceLocation; }; class LogSink { public: virtual ~LogSink() noexcept = default; virtual void handleMessage(const LogMessage& message) MIJIN_NOEXCEPT = 0; }; template typename TAllocator = std::allocator> class Logger { private: std::vector> mSinks; public: explicit Logger(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) : mSinks(std::move(allocator)) {} Logger(const Logger&) = default; Logger(Logger&&) = default; Logger& operator=(const Logger&) = default; Logger& operator=(Logger&&) = default; void addSink(LogSink& sink) { mSinks.push_back(&sink); } void postMessage(const LogMessage& message) const MIJIN_NOEXCEPT { for (LogSink* sink: mSinks) { sink->handleMessage(message); } } void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation, const char* msg) const MIJIN_NOEXCEPT { postMessage({ .text = msg, .channel = &channel, .level = &level, .sourceLocation = std::move(sourceLocation) }); } template void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation, std::format_string fmt, TArgs&& ... args) const MIJIN_NOEXCEPT_IF(noexcept(std::declval>().allocate(1))) { std::basic_string, TAllocator> buffer(TAllocator(mSinks.get_allocator())); std::format_to(std::back_inserter(buffer), fmt, std::forward(args)...); log(level, channel, std::move(sourceLocation), buffer.c_str()); } }; #define MIJIN_DECLARE_LOG_CHANNEL(cnlName) \ namespace mijin_log_channel \ { \ extern const ::mijin::LogChannel cnlName; \ } #define MIJIN_DEFINE_LOG_CHANNEL(cnlName) \ namespace mijin_log_channel \ { \ const ::mijin::LogChannel cnlName { \ .name = #cnlName \ }; \ } #define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) \ namespace mijin_log_level \ { \ inline constexpr ::mijin::LogLevel lvlName{ \ .name = #lvlName, \ .value = lvlValue \ }; \ } MIJIN_DECLARE_LOG_CHANNEL(GENERAL) MIJIN_DEFINE_LOG_LEVEL(DEBUG, -1000) MIJIN_DEFINE_LOG_LEVEL(VERBOSE, -500) MIJIN_DEFINE_LOG_LEVEL(INFO, 0) MIJIN_DEFINE_LOG_LEVEL(WARNING, 500) MIJIN_DEFINE_LOG_LEVEL(ERROR, 1000) #if defined(MIJIN_MIN_LOGLEVEL) inline constexpr int MIN_LOG_LEVEL = static_cast(MIJIN_MIN_LOGLEVEL); #elif defined(MIJIN_DEBUG) inline constexpr int MIN_LOG_LEVEL = mijin_log_level::DEBUG.value; #else inline constexpr int MIN_LOG_LEVEL = mijin_log_level::VERBOSE.value; #endif #define MIJIN_IMPORT_LOG_DEFAULTS \ namespace mijin_log_channel \ { \ using ::mijin::mijin_log_channel::GENERAL; \ } \ \ namespace mijin_log_level \ { \ using ::mijin::mijin_log_level::DEBUG; \ using ::mijin::mijin_log_level::VERBOSE; \ using ::mijin::mijin_log_level::INFO; \ using ::mijin::mijin_log_level::WARNING; \ using ::mijin::mijin_log_level::ERROR; \ } #define MIJIN_LOG_ALWAYS(level, channel, ...) mijin__getLogger__().log( \ mijin_log_level::level, mijin_log_channel::channel, std::source_location::current(), __VA_ARGS__ \ ) #define MIJIN_LOG(level, channel, ...) \ if constexpr (mijin_log_level::level.value < mijin::MIN_LOG_LEVEL) {} \ else MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__) #define MIJIN_SET_CLASS_LOGGER(loggerExpr) \ const auto& mijin__getLogger__() const noexcept \ { \ return loggerExpr; \ } #define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \ auto mijin__getLogger__ = [&]() -> const auto& \ { \ return loggerExpr; \ }; } // namespace mijin #endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)