177 lines
5.9 KiB
C++
177 lines
5.9 KiB
C++
|
|
#pragma once
|
|
|
|
#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
|
|
#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1
|
|
|
|
#include <flat_map>
|
|
#include <format>
|
|
#include <variant>
|
|
#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<allocator_type_for<char> TAllocator = std::allocator<char>>
|
|
class LogFormatter
|
|
{
|
|
public:
|
|
using string_t = std::basic_string<char, std::char_traits<char>, TAllocator>;
|
|
|
|
virtual ~LogFormatter() noexcept = default;
|
|
|
|
virtual void format(const LogMessage& message, string_t& outFormatted) noexcept = 0;
|
|
};
|
|
|
|
template<allocator_type_for<char> TAllocator = std::allocator<char>>
|
|
class SimpleLogFormatter : public LogFormatter<TAllocator>
|
|
{
|
|
public:
|
|
using typename LogFormatter<TAllocator>::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<template<typename> typename TAllocator = std::allocator,
|
|
deleter_type<LogFormatter<TAllocator<char>>> TDeleter = AllocatorDeleter<TAllocator<LogFormatter<TAllocator<char>>>>>
|
|
requires(allocator_type<TAllocator<char>>)
|
|
class FormattingLogSink : public LogSink
|
|
{
|
|
public:
|
|
using allocator_t = TAllocator<char>;
|
|
using formatter_t = LogFormatter<allocator_t>;
|
|
using formatter_deleter_t = TDeleter;
|
|
using formatter_ptr_t = DynamicPointer<formatter_t, formatter_deleter_t>;
|
|
using string_t = formatter_t::string_t;
|
|
private:
|
|
not_null_t<formatter_ptr_t> mFormatter;
|
|
string_t mBuffer;
|
|
public:
|
|
explicit FormattingLogSink(not_null_t<formatter_ptr_t> formatter, TAllocator<char> allocator = {})
|
|
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<char>>)
|
|
: 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<allocator_type_for<char> TAllocator>
|
|
void SimpleLogFormatter<TAllocator>::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<decltype(value)>;
|
|
|
|
// if there is no format, just directly print the value
|
|
if (argFormat.empty())
|
|
{
|
|
if constexpr (std::is_arithmetic_v<type_t>)
|
|
{
|
|
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)
|