#pragma once #if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED) #define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1 #include #include #include #include #include #include #include "../internal/common.hpp" #include "../util/annot.hpp" #include "../util/iterators.hpp" namespace mijin { #if !defined(MIJIN_LOG_LEVEL_VALUE_DEBUG) #define MIJIN_LOG_LEVEL_VALUE_DEBUG -1000 #endif #if !defined(MIJIN_LOG_LEVEL_VALUE_VERBOSE) #define MIJIN_LOG_LEVEL_VALUE_VERBOSE -500 #endif #if !defined(MIJIN_LOG_LEVEL_VALUE_INFO) #define MIJIN_LOG_LEVEL_VALUE_INFO 0 #endif #if !defined(MIJIN_LOG_LEVEL_VALUE_WARNING) #define MIJIN_LOG_LEVEL_VALUE_WARNING 500 #endif #if !defined(MIJIN_LOG_LEVEL_VALUE_ERROR) #define MIJIN_LOG_LEVEL_VALUE_ERROR 1000 #endif #if !defined(MIJIN_FUNCNAME_GET_LOGGER) #define MIJIN_FUNCNAME_GET_LOGGER mijin__getLogger__ #endif #if !defined(MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE) #define MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE mijin__getMinLogLevelCompile #endif #if !defined(MIJIN_NSNAME_LOG_LEVEL) #define MIJIN_NSNAME_LOG_LEVEL mijin_log_level #endif #if !defined(MIJIN_NSNAME_LOG_CHANNEL) #define MIJIN_NSNAME_LOG_CHANNEL mijin_log_channel #endif template struct BaseLogLevel { using char_t = TChar; const char_t* name; int value; explicit operator int() const MIJIN_NOEXCEPT { return value; } auto operator<=>(const BaseLogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; } }; MIJIN_DEFINE_CHAR_VERSIONS(LogLevel) template struct BaseLogChannel { using char_t = TChar; const char_t* name; }; MIJIN_DEFINE_CHAR_VERSIONS(LogChannel) template struct BaseLogMessage { using char_t = TChar; const char_t* text; const BaseLogChannel* channel; const BaseLogLevel* level; std::source_location sourceLocation; }; MIJIN_DEFINE_CHAR_VERSIONS(LogMessage) template class BaseLogSink { public: using char_t = TChar; using message_t = BaseLogMessage; virtual ~BaseLogSink() noexcept = default; virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0; }; MIJIN_DEFINE_CHAR_VERSIONS(LogSink) template class BaseLogFilter { public: using char_t = TChar; using message_t = BaseLogMessage; virtual ~BaseLogFilter() noexcept = default; virtual bool shouldShow(const message_t& message) MIJIN_NOEXCEPT = 0; }; MIJIN_DEFINE_CHAR_VERSIONS(LogFilter) #define LOGGER_COMMON_ARGS(chr_type) template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR template, LOGGER_COMMON_ARGS(TChar)> class BaseLogger { public: using char_t = TChar; using traits_t = TTraits; using allocator_t = TAllocator; using sink_t = BaseLogSink; using filter_t = BaseLogFilter; using level_t = BaseLogLevel; using channel_t = BaseLogChannel; using message_t = BaseLogMessage; using string_t = std::basic_string; private: struct SinkEntry { sink_t* sink; filter_t* filter; }; std::vector> mSinks; mutable std::mutex mMutex; public: explicit BaseLogger(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v, TAllocator&&>)) : mSinks(TAllocator(std::move(allocator))) {} BaseLogger(const BaseLogger&) = default; BaseLogger(BaseLogger&&) = default; BaseLogger& operator=(const BaseLogger&) = default; BaseLogger& operator=(BaseLogger&&) = default; void addSink(sink_t& sink) { std::unique_lock _(mMutex); mSinks.push_back({&sink, nullptr}); } void addSink(sink_t& sink, filter_t& filter) { std::unique_lock _(mMutex); mSinks.push_back({&sink, &filter}); } void postMessage(const message_t& message) const MIJIN_NOEXCEPT { std::unique_lock _(mMutex); for (const SinkEntry& entry : mSinks) { if (entry.filter != nullptr && !entry.filter->shouldShow(message)) { continue; } entry.sink->handleMessage(message); } } void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, const char_t* msg) const MIJIN_NOEXCEPT { postMessage({ .text = msg, .channel = &channel, .level = &level, .sourceLocation = std::move(sourceLocation) }); } template void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, std::basic_format_string...> fmt, TArgs&& ... args) const MIJIN_NOEXCEPT { // TODO: make the logger use a traits struct to make this adjustable static constexpr std::size_t BUFFER_SIZE = 256; std::array buffer; // first try to write into a buffer on the stack FixedArrayOutputIterator itAfter = std::format_to(FixedArrayOutputIterator(buffer), fmt, std::forward(args)...); *itAfter = '\0'; ++itAfter; if (!itAfter.didOverflow()) { log(level, channel, std::move(sourceLocation), buffer.data()); return; } // if that didn't work, allocate more space const std::size_t newBufferSize = itAfter.getCounter(); char_t* newBuffer = static_cast(alloca(newBufferSize * sizeof(char_t))); const std::format_to_n_result result = std::format_to_n(newBuffer, newBufferSize - 1, fmt, std::forward(args)...); *result.out = '\0'; log(level, channel, std::move(sourceLocation), newBuffer); } }; #define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits, TAllocator MIJIN_DEFINE_CHAR_VERSIONS_TMPL(Logger, LOGGER_COMMON_ARGS, LOGGER_SET_ARGS) #undef LOGGER_COMMON_ARGS #undef LOGGER_SET_ARGS #define MIJIN_DECLARE_LOG_CHANNEL_BASE(chr_type, cnlName) \ namespace MIJIN_NSNAME_LOG_CHANNEL \ { \ extern const ::mijin::BaseLogChannel cnlName; \ } #define MIJIN_DECLARE_LOG_CHANNEL(cnlName) MIJIN_DECLARE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName) #define MIJIN_DEFINE_LOG_CHANNEL_BASE(chr_type, cnlName) \ namespace MIJIN_NSNAME_LOG_CHANNEL \ { \ const ::mijin::BaseLogChannel cnlName { \ .name = MIJIN_SMART_STRINGIFY(chr_type, cnlName) \ }; \ } #define MIJIN_DEFINE_LOG_CHANNEL(cnlName) MIJIN_DEFINE_LOG_CHANNEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, cnlName) #define MIJIN_DEFINE_LOG_LEVEL_BASE(chr_type, lvlName, lvlValue) \ namespace MIJIN_NSNAME_LOG_LEVEL \ { \ inline constexpr ::mijin::BaseLogLevel lvlName{ \ .name = MIJIN_SMART_STRINGIFY(chr_type, lvlName), \ .value = lvlValue \ }; \ } #define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) MIJIN_DEFINE_LOG_LEVEL_BASE(MIJIN_DEFAULT_CHAR_TYPE, lvlName, lvlValue) #if defined(MIJIN_MIN_LOGLEVEL_COMPILE) inline constexpr int MIN_LOG_LEVEL_COMPILE = static_cast(MIJIN_MIN_LOGLEVEL_COMPILE); #elif defined(MIJIN_DEBUG) inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_DEBUG; #else inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_VERBOSE; #endif #define MIJIN_DEFINE_DEFAULT_LOG_LEVELS \ MIJIN_DEFINE_LOG_LEVEL(DEBUG, MIJIN_LOG_LEVEL_VALUE_DEBUG) \ MIJIN_DEFINE_LOG_LEVEL(VERBOSE, MIJIN_LOG_LEVEL_VALUE_VERBOSE) \ MIJIN_DEFINE_LOG_LEVEL(INFO, MIJIN_LOG_LEVEL_VALUE_INFO) \ MIJIN_DEFINE_LOG_LEVEL(WARNING, MIJIN_LOG_LEVEL_VALUE_WARNING) \ MIJIN_DEFINE_LOG_LEVEL(ERROR, MIJIN_LOG_LEVEL_VALUE_ERROR) #define MIJIN_LOG_LEVEL_OBJECT(level) MIJIN_NSNAME_LOG_LEVEL::level #define MIJIN_LOG_CHANNEL_OBJECT(channel) MIJIN_NSNAME_LOG_CHANNEL::channel #define MIJIN_LOG_ALWAYS(level, channel, ...) MIJIN_FUNCNAME_GET_LOGGER().log( \ MIJIN_LOG_LEVEL_OBJECT(level), MIJIN_LOG_CHANNEL_OBJECT(channel), std::source_location::current(), __VA_ARGS__ \ ) #define MIJIN_LOG(level, channel, ...) \ if constexpr (MIJIN_LOG_LEVEL_OBJECT(level).value < MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE()) {} \ else MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__) #define MIJIN_SET_CLASS_LOGGER(loggerExpr) \ const auto& MIJIN_FUNCNAME_GET_LOGGER() const noexcept \ { \ return loggerExpr; \ } #define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \ auto MIJIN_FUNCNAME_GET_LOGGER = [&]() -> const auto& \ { \ return loggerExpr; \ }; #define MIJIN_SET_CLASS_MIN_LOG_LEVEL_COMPILE(level) \ int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT \ { \ return MIJIN_LOG_LEVEL_OBJECT(level).value; \ } #define MIJIN_SET_SCOPE_MIN_LOG_LEVEL_COMPILE(level) \ auto MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE = []() -> int \ { \ return MIJIN_LOG_LEVEL_OBJECT(level).value; \ }; } // namespace mijin inline constexpr int MIJIN_FUNCNAME_MIN_LOG_LEVEL_COMPILE() MIJIN_NOEXCEPT { return ::mijin::MIN_LOG_LEVEL_COMPILE; } #endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)