#pragma once #if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED) #define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1 #include #include #include #include "./logger.hpp" #include "../memory/dynamic_pointer.hpp" #include "../memory/memutil.hpp" #include "../memory/virtual_allocator.hpp" #include "../util/annot.hpp" #include "../util/concepts.hpp" namespace mijin { template TAllocator = std::allocator> class LogFormatter { public: using string_t = std::basic_string, TAllocator>; virtual ~LogFormatter() noexcept = default; virtual void format(const LogMessage& message, string_t& outFormatted) noexcept = 0; }; template TAllocator = std::allocator> class SimpleLogFormatter : public LogFormatter { public: using typename LogFormatter::string_t; private: string_t mFormat; public: explicit SimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)) {} void format(const LogMessage& message, string_t& outFormatted) noexcept override; }; template typename TAllocator = std::allocator, deleter_type>> TDeleter = AllocatorDeleter>>>> requires(allocator_type>) class FormattingLogSink : public LogSink { public: using allocator_t = TAllocator; using formatter_t = LogFormatter; using formatter_deleter_t = TDeleter; using formatter_ptr_t = DynamicPointer; using string_t = formatter_t::string_t; private: not_null_t mFormatter; string_t mBuffer; public: explicit FormattingLogSink(not_null_t formatter, TAllocator allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>) : mFormatter(std::move(formatter)), mBuffer(std::move(allocator)) {} virtual void handleMessageFormatted(const LogMessage& message, const char* formatted) MIJIN_NOEXCEPT = 0; void handleMessage(const LogMessage& message) noexcept override { mBuffer.clear(); mFormatter->format(message, mBuffer); handleMessageFormatted(message, mBuffer.c_str()); } }; template TAllocator> void SimpleLogFormatter::format(const LogMessage& message, string_t& outFormatted) noexcept { for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos) { if (*pos == '{') { ++pos; if (*pos == '{') { // double { outFormatted += '{'; continue; } const auto argStart = pos; static const std::string_view endChars = ":}"; pos = std::find_first_of(pos, mFormat.end(), endChars.begin(), endChars.end()); MIJIN_ASSERT(pos != mFormat.end(), "Invalid format."); const std::string_view argName(argStart, pos); std::string argFormat; if (*pos == ':') { const auto formatStart = pos; pos = std::find(pos, mFormat.end(), '}'); MIJIN_ASSERT(pos != mFormat.end(), "Invalid format."); argFormat = std::string_view(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 (std::is_arithmetic_v) { std::format_to(std::back_inserter(outFormatted), "{}", value); } else { outFormatted += value; } return; } // first copy the format string + braces into the buffer const auto formatStart = outFormatted.size(); outFormatted += '{'; outFormatted += argFormat; outFormatted += '}'; const auto formatEnd = outFormatted.size(); auto format = std::string_view(outFormatted).substr(formatStart, formatEnd - formatStart); // then append the formatted text std::vformat_to(std::back_inserter(outFormatted), format, std::make_format_args(value)); // and then remove the format from the buffer again outFormatted.erase(formatStart, formatEnd - formatStart); }; if (argName == "text") { formatInline(message.text); } else if (argName == "channel") { formatInline(message.channel->name); } else if (argName == "file") { formatInline(message.sourceLocation.file_name()); } else if (argName == "function") { formatInline(message.sourceLocation.function_name()); } else if (argName == "line") { formatInline(message.sourceLocation.line()); } else if (argName == "column") { formatInline(message.sourceLocation.column()); } else if (argName == "level") { formatInline(message.level->name); } else { MIJIN_ERROR("Invalid format argument name."); } } else { outFormatted += *pos; } } } } // namespace mijin #endif // !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)