#pragma once #if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED) #define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1 #include #include #include #include "./logger.hpp" #include "../internal/common.hpp" #include "../memory/dynamic_pointer.hpp" #include "../memory/memutil.hpp" #include "../memory/virtual_allocator.hpp" #include "../util/annot.hpp" #include "../util/ansi_colors.hpp" #include "../util/concepts.hpp" #include "../util/string.hpp" namespace mijin { #define FORMATTER_COMMON_ARGS(chr_type) allocator_type_for TAllocator = MIJIN_DEFAULT_ALLOCATOR template, FORMATTER_COMMON_ARGS(TChar)> class BaseLogFormatter { public: using char_t = TChar; using traits_t = TTraits; using allocator_t = TAllocator; using string_t = std::basic_string; virtual ~BaseLogFormatter() noexcept = default; virtual void format(const LogMessage& message, string_t& outFormatted) = 0; }; template, FORMATTER_COMMON_ARGS(TChar)> class BaseSimpleLogFormatter : public BaseLogFormatter { public: using char_t = TChar; using traits_t = TTraits; using allocator_t = TAllocator; using base_t = BaseLogFormatter; using typename base_t::string_t; using string_view_t = std::basic_string_view; private: string_t mFormat; string_t mFormatBuffer; public: explicit BaseSimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)), mFormatBuffer(mFormat.get_allocator()) {} void format(const LogMessage& message, string_t& outFormatted) override; private: void formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted); }; #define FORMATTER_SET_ARGS(chr_type) chr_type, std::char_traits, TAllocator MIJIN_DEFINE_CHAR_VERSIONS_TMPL(LogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS) MIJIN_DEFINE_CHAR_VERSIONS_TMPL(SimpleLogFormatter, FORMATTER_COMMON_ARGS, FORMATTER_SET_ARGS) #undef FORMATTER_COMMON_ARGS #undef FORMATTER_SET_ARGS #define MIJIN_FORMATTING_SINK_COMMON_ARGS(chr_type) \ typename TTraits = std::char_traits, \ template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, \ deleter_type>> TDeleter \ = AllocatorDeleter>>> #define MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT \ typename TChar = MIJIN_DEFAULT_CHAR_TYPE, \ MIJIN_FORMATTING_SINK_COMMON_ARGS(TChar) #define MIJIN_FORMATTING_SINK_TMP_ARG_NAMES TChar, TTraits, TAllocator, TDeleter template requires(allocator_type>) class BaseFormattingLogSink : public BaseLogSink { public: using base_t = BaseLogSink; using char_t = TChar; using traits_t = TTraits; using allocator_t = TAllocator; using formatter_t = BaseLogFormatter; using formatter_deleter_t = TDeleter; using formatter_ptr_t = DynamicPointer; using string_t = formatter_t::string_t; using typename base_t::message_t; private: not_null_t mFormatter; string_t mBuffer; public: explicit BaseFormattingLogSink(not_null_t formatter, allocator_t allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v) : mFormatter(std::move(formatter)), mBuffer(std::move(allocator)) {} virtual void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT = 0; void handleMessage(const message_t& message) noexcept override { mBuffer.clear(); mFormatter->format(message, mBuffer); handleMessageFormatted(message, mBuffer.c_str()); } }; #define SINK_SET_ARGS(chr_type) chr_type, std::char_traits, TAllocator, TDeleter MIJIN_DEFINE_CHAR_VERSIONS_TMPL(FormattingLogSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS) #undef SINK_SET_ARGS template TAllocator> void BaseSimpleLogFormatter::format(const LogMessage& message, string_t& outFormatted) { mFormatBuffer.clear(); for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos) { if (*pos == MIJIN_SMART_QUOTE(char_t, '{')) { ++pos; if (*pos == MIJIN_SMART_QUOTE(char_t, '{')) { // double { outFormatted += MIJIN_SMART_QUOTE(char_t, '{'); continue; } const auto argStart = pos; static const string_view_t endChars = MIJIN_SMART_QUOTE(char_t, ":}"); pos = std::find_first_of(pos, mFormat.end(), endChars.begin(), endChars.end()); MIJIN_ASSERT(pos != mFormat.end(), "Invalid format."); const string_view_t argName(argStart, pos); string_view_t argFormat; if (*pos == ':') { const auto formatStart = pos; pos = std::find(pos, mFormat.end(), MIJIN_SMART_QUOTE(char_t, '}')); MIJIN_ASSERT(pos != mFormat.end(), "Invalid format."); argFormat = string_view_t(formatStart, pos); } // small utility that uses the provided string buffer for storing the format string auto formatInline = [&](const auto& value) { using type_t = std::decay_t; // if there is no format, just directly print the value if (argFormat.empty()) { if constexpr (is_char_v) { convertStringType(string_view_t(&value, 1), outFormatted); } else if constexpr (std::is_arithmetic_v) { std::format_to(std::back_inserter(outFormatted), MIJIN_SMART_QUOTE(char_t, "{}"), value); } else if constexpr (is_string_v || is_cstring_v) { convertStringType(value, outFormatted); } else { static_assert(always_false_v); outFormatted += value; } return; } // first copy the format string + braces into the buffer const auto formatStart = mFormatBuffer.size(); mFormatBuffer += '{'; mFormatBuffer += argFormat; mFormatBuffer += '}'; const auto formatEnd = mFormatBuffer.size(); auto doFormatTo = [](string_t& string, string_view_t format, const auto& value) { if constexpr (std::is_same_v) { std::vformat_to(std::back_inserter(string), format, std::make_format_args(value)); } else if constexpr (std::is_same_v) { std::vformat_to(std::back_inserter(string), format, std::make_wformat_args(value)); } else { static_assert(always_false_v, "Cannot format this char type."); } }; auto doFormat = [&](const auto& value) { auto format = string_view_t(mFormatBuffer).substr(formatStart, formatEnd - formatStart); doFormatTo(outFormatted, format, value); }; if constexpr (is_char_v && !std::is_same_v) { static_assert(always_false_v, "TODO..."); } else if constexpr ((is_string_v || is_cstring_v) && !std::is_same_v, char_t>) { // different string type, needs to be converted const auto convertedStart = mFormatBuffer.size(); convertStringType(value, mFormatBuffer); const auto convertedEnd = mFormatBuffer.size(); // then we can format it auto converted = string_view_t(mFormatBuffer).substr(convertedStart, mFormatBuffer.size() - convertedEnd); doFormat(converted); } else { // nothing special doFormat(value); } }; if (argName == MIJIN_SMART_QUOTE(char_t, "text")) { formatInline(message.text); } else if (argName == MIJIN_SMART_QUOTE(char_t, "channel")) { formatInline(message.channel->name); } else if (argName == MIJIN_SMART_QUOTE(char_t, "file")) { formatInline(message.sourceLocation.file_name()); } else if (argName == MIJIN_SMART_QUOTE(char_t, "function")) { formatInline(message.sourceLocation.function_name()); } else if (argName == MIJIN_SMART_QUOTE(char_t, "line")) { formatInline(message.sourceLocation.line()); } else if (argName == MIJIN_SMART_QUOTE(char_t, "column")) { formatInline(message.sourceLocation.column()); } else if (argName == MIJIN_SMART_QUOTE(char_t, "level")) { formatInline(message.level->name); } else if (argName == MIJIN_SMART_QUOTE(char_t, "ansi")) { formatAnsiSequence(message, argFormat.substr(1), outFormatted); } else { MIJIN_ERROR("Invalid format argument name."); } } else { outFormatted += *pos; } } } template TAllocator> void BaseSimpleLogFormatter::formatAnsiSequence(const LogMessage& message, string_view_t ansiName, string_t& outFormatted) { std::size_t numParts = 0; const auto formatParts = splitFixed<4>(ansiName, ",", {}, &numParts); outFormatted += MIJIN_SMART_QUOTE(char_t, "\033["); for (std::size_t partIdx = 0; partIdx < numParts; ++partIdx) { const string_view_t& part = formatParts[partIdx]; if (partIdx > 0) { outFormatted += MIJIN_SMART_QUOTE(char_t, ','); } if (part == MIJIN_SMART_QUOTE(char_t, "reset")) { outFormatted += BaseAnsiFontEffects::RESET; } else if (part == MIJIN_SMART_QUOTE(char_t, "level_color")) { const int levelValue = message.level->value; if (levelValue < MIJIN_LOG_LEVEL_VALUE_VERBOSE) { outFormatted += BaseAnsiFontEffects::FG_CYAN; } else if (levelValue < MIJIN_LOG_LEVEL_VALUE_INFO) { outFormatted += BaseAnsiFontEffects::FG_WHITE; } else if (levelValue < MIJIN_LOG_LEVEL_VALUE_WARNING) { outFormatted += BaseAnsiFontEffects::FG_BRIGHT_WHITE; } else if (levelValue < MIJIN_LOG_LEVEL_VALUE_ERROR) { outFormatted += BaseAnsiFontEffects::FG_YELLOW; } else { outFormatted += BaseAnsiFontEffects::FG_RED; } } else { MIJIN_ERROR("Invalid format ansi font effect name."); } } outFormatted += MIJIN_SMART_QUOTE(char_t, 'm'); } } // namespace mijin #endif // !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)