324 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
 | 
						|
#pragma once
 | 
						|
 | 
						|
#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
 | 
						|
#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1
 | 
						|
 | 
						|
#include <format>
 | 
						|
#include <variant>
 | 
						|
#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<chr_type> TAllocator = MIJIN_DEFAULT_ALLOCATOR<chr_type>
 | 
						|
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
 | 
						|
        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<char_t, traits_t, allocator_t>;
 | 
						|
 | 
						|
    virtual ~BaseLogFormatter() noexcept = default;
 | 
						|
 | 
						|
    virtual void format(const LogMessage& message, string_t& outFormatted) = 0;
 | 
						|
};
 | 
						|
 | 
						|
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>,
 | 
						|
        FORMATTER_COMMON_ARGS(TChar)>
 | 
						|
class BaseSimpleLogFormatter : public BaseLogFormatter<TChar, TTraits, TAllocator>
 | 
						|
{
 | 
						|
public:
 | 
						|
    using char_t = TChar;
 | 
						|
    using traits_t = TTraits;
 | 
						|
    using allocator_t = TAllocator;
 | 
						|
    using base_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
 | 
						|
    using typename base_t::string_t;
 | 
						|
    using string_view_t = std::basic_string_view<char_t, traits_t>;
 | 
						|
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<chr_type>, 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<chr_type>,                                                \
 | 
						|
    template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR,                             \
 | 
						|
    deleter_type<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>> TDeleter              \
 | 
						|
        = AllocatorDeleter<TAllocator<BaseLogFormatter<chr_type, TTraits, TAllocator<chr_type>>>>
 | 
						|
 | 
						|
#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<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
 | 
						|
        requires(allocator_type<TAllocator<TChar>>)
 | 
						|
class BaseFormattingLogSink : public BaseLogSink<TChar>
 | 
						|
{
 | 
						|
public:
 | 
						|
    using base_t = BaseLogSink<TChar>;
 | 
						|
 | 
						|
    using char_t = TChar;
 | 
						|
    using traits_t = TTraits;
 | 
						|
    using allocator_t = TAllocator<TChar>;
 | 
						|
    using formatter_t = BaseLogFormatter<char_t, traits_t, allocator_t>;
 | 
						|
    using formatter_deleter_t = TDeleter;
 | 
						|
    using formatter_ptr_t = DynamicPointer<formatter_t, formatter_deleter_t>;
 | 
						|
    using string_t = formatter_t::string_t;
 | 
						|
    using typename base_t::message_t;
 | 
						|
private:
 | 
						|
    not_null_t<formatter_ptr_t> mFormatter;
 | 
						|
    string_t mBuffer;
 | 
						|
public:
 | 
						|
    explicit BaseFormattingLogSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
 | 
						|
        MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
 | 
						|
        : 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<chr_type>, TAllocator, TDeleter
 | 
						|
 | 
						|
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(FormattingLogSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
 | 
						|
 | 
						|
#undef SINK_SET_ARGS
 | 
						|
 | 
						|
template<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
 | 
						|
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::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<decltype(value)>;
 | 
						|
 | 
						|
                // if there is no format, just directly print the value
 | 
						|
                if (argFormat.empty())
 | 
						|
                {
 | 
						|
                    if constexpr (is_char_v<type_t>)
 | 
						|
                    {
 | 
						|
                        convertStringType(string_view_t(&value, 1), outFormatted);
 | 
						|
                    }
 | 
						|
                    else if constexpr (std::is_arithmetic_v<type_t>)
 | 
						|
                    {
 | 
						|
                        std::format_to(std::back_inserter(outFormatted), MIJIN_SMART_QUOTE(char_t, "{}"), value);
 | 
						|
                    }
 | 
						|
                    else if constexpr (is_string_v<type_t> || is_cstring_v<type_t>)
 | 
						|
                    {
 | 
						|
                        convertStringType(value, outFormatted);
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        static_assert(always_false_v<type_t>);
 | 
						|
                        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<char_t, char>)
 | 
						|
                    {
 | 
						|
                        std::vformat_to(std::back_inserter(string), format, std::make_format_args(value));
 | 
						|
                    }
 | 
						|
                    else if constexpr (std::is_same_v<char_t, wchar_t>)
 | 
						|
                    {
 | 
						|
                        std::vformat_to(std::back_inserter(string), format, std::make_wformat_args(value));
 | 
						|
                    }
 | 
						|
                    else
 | 
						|
                    {
 | 
						|
                        static_assert(always_false_v<char_t>, "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<type_t> && !std::is_same_v<char_t, type_t>)
 | 
						|
                {
 | 
						|
                    static_assert(always_false_v<type_t>, "TODO...");
 | 
						|
                }
 | 
						|
                else if constexpr ((is_string_v<type_t> || is_cstring_v<type_t>) && !std::is_same_v<str_char_type_t<type_t>, 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<typename TChar, typename TTraits, allocator_type_for<TChar> TAllocator>
 | 
						|
void BaseSimpleLogFormatter<TChar, TTraits, TAllocator>::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<char_t>::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<char_t>::FG_CYAN;
 | 
						|
            }
 | 
						|
            else if (levelValue < MIJIN_LOG_LEVEL_VALUE_INFO)
 | 
						|
            {
 | 
						|
                outFormatted += BaseAnsiFontEffects<char_t>::FG_WHITE;
 | 
						|
            }
 | 
						|
            else if (levelValue < MIJIN_LOG_LEVEL_VALUE_WARNING)
 | 
						|
            {
 | 
						|
                outFormatted += BaseAnsiFontEffects<char_t>::FG_BRIGHT_WHITE;
 | 
						|
            }
 | 
						|
            else if (levelValue < MIJIN_LOG_LEVEL_VALUE_ERROR)
 | 
						|
            {
 | 
						|
                outFormatted += BaseAnsiFontEffects<char_t>::FG_YELLOW;
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                outFormatted += BaseAnsiFontEffects<char_t>::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)
 |