271 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| 
 | |
| #pragma once
 | |
| 
 | |
| #if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
 | |
| #define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1
 | |
| 
 | |
| #include <cstdint>
 | |
| #include <format>
 | |
| #include <source_location>
 | |
| #include <string>
 | |
| #include <vector>
 | |
| #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<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
 | |
| 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<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
 | |
| struct BaseLogChannel
 | |
| {
 | |
|     using char_t = TChar;
 | |
| 
 | |
|     const char_t* name;
 | |
| };
 | |
| 
 | |
| MIJIN_DEFINE_CHAR_VERSIONS(LogChannel)
 | |
| 
 | |
| template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
 | |
| struct BaseLogMessage
 | |
| {
 | |
|     using char_t = TChar;
 | |
| 
 | |
|     const char_t* text;
 | |
|     const BaseLogChannel<char_t>* channel;
 | |
|     const BaseLogLevel<char_t>* level;
 | |
|     std::source_location sourceLocation;
 | |
| };
 | |
| 
 | |
| MIJIN_DEFINE_CHAR_VERSIONS(LogMessage)
 | |
| 
 | |
| template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
 | |
| class BaseLogSink
 | |
| {
 | |
| public:
 | |
|     using char_t = TChar;
 | |
|     using message_t = BaseLogMessage<char_t>;
 | |
| 
 | |
|     virtual ~BaseLogSink() noexcept = default;
 | |
| 
 | |
|     virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0;
 | |
| };
 | |
| 
 | |
| MIJIN_DEFINE_CHAR_VERSIONS(LogSink)
 | |
| 
 | |
| #define LOGGER_COMMON_ARGS(chr_type) template<typename T> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR
 | |
| template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>, LOGGER_COMMON_ARGS(TChar)>
 | |
| class BaseLogger
 | |
| {
 | |
| public:
 | |
|     using char_t = TChar;
 | |
|     using traits_t = TTraits;
 | |
|     using allocator_t = TAllocator<char_t>;
 | |
| 
 | |
|     using sink_t = BaseLogSink<char_t>;
 | |
|     using level_t = BaseLogLevel<char_t>;
 | |
|     using channel_t = BaseLogChannel<char_t>;
 | |
|     using message_t = BaseLogMessage<char_t>;
 | |
|     using string_t = std::basic_string<char_t, traits_t, allocator_t>;
 | |
| private:
 | |
|     std::vector<sink_t*, TAllocator<sink_t*>> mSinks;
 | |
| public:
 | |
|     explicit BaseLogger(TAllocator<sink_t*> allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<sink_t*>>)
 | |
|         : mSinks(std::move(allocator))
 | |
|     {}
 | |
| 
 | |
|     BaseLogger(const BaseLogger&) = default;
 | |
|     BaseLogger(BaseLogger&&) = default;
 | |
|     BaseLogger& operator=(const BaseLogger&) = default;
 | |
|     BaseLogger& operator=(BaseLogger&&) = default;
 | |
| 
 | |
|     void addSink(sink_t& sink)
 | |
|     {
 | |
|         mSinks.push_back(&sink);
 | |
|     }
 | |
| 
 | |
|     void postMessage(const message_t& message) const MIJIN_NOEXCEPT
 | |
|     {
 | |
|         for (sink_t* sink: mSinks)
 | |
|         {
 | |
|             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<typename... TArgs>
 | |
|     void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation,
 | |
|              std::basic_format_string<char_t, std::type_identity_t<TArgs>...> 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<char_t, BUFFER_SIZE> buffer;
 | |
| 
 | |
|         // first try to write into a buffer on the stack
 | |
|         FixedArrayOutputIterator itAfter = std::format_to(FixedArrayOutputIterator(buffer), fmt, std::forward<TArgs>(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<char_t*>(alloca(newBufferSize * sizeof(char_t)));
 | |
|         const std::format_to_n_result result = std::format_to_n(newBuffer, newBufferSize - 1, fmt, std::forward<TArgs>(args)...);
 | |
|         *result.out = '\0';
 | |
|         log(level, channel, std::move(sourceLocation), newBuffer);
 | |
|     }
 | |
| };
 | |
| 
 | |
| #define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, 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<chr_type> 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<chr_type> 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<chr_type> 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<int>(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)
 |