Massively overengineered logger to support different character types.

This commit is contained in:
2025-06-21 14:16:19 +02:00
parent 061c58ef41
commit b91eb34789
9 changed files with 621 additions and 157 deletions

View File

@@ -8,61 +8,98 @@
#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/concepts.hpp"
#include "../util/string.hpp"
namespace mijin
{
template<allocator_type_for<char> TAllocator = std::allocator<char>>
class LogFormatter
#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 string_t = std::basic_string<char, std::char_traits<char>, TAllocator>;
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 ~LogFormatter() noexcept = default;
virtual ~BaseLogFormatter() 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>
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 typename LogFormatter<TAllocator>::string_t;
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;
private:
string_t mFormat;
string_t mFormatBuffer;
public:
explicit SimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)) {}
explicit BaseSimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)), mFormatBuffer(mFormat.get_allocator()) {}
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
#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 allocator_t = TAllocator<char>;
using formatter_t = LogFormatter<allocator_t>;
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 FormattingLogSink(not_null_t<formatter_ptr_t> formatter, TAllocator<char> allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<char>>)
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 LogMessage& message, const char* formatted) MIJIN_NOEXCEPT = 0;
virtual void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT = 0;
void handleMessage(const LogMessage& message) noexcept override
void handleMessage(const message_t& message) noexcept override
{
mBuffer.clear();
mFormatter->format(message, mBuffer);
@@ -70,33 +107,42 @@ public:
}
};
template<allocator_type_for<char> TAllocator>
void SimpleLogFormatter<TAllocator>::format(const LogMessage& message, string_t& outFormatted) noexcept
#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) noexcept
{
using string_view_t = std::basic_string_view<char_t, traits_t>;
mFormatBuffer.clear();
for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos)
{
if (*pos == '{')
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
{
++pos;
if (*pos == '{')
if (*pos == MIJIN_SMART_QUOTE(char_t, '{'))
{
// double {
outFormatted += '{';
outFormatted += MIJIN_SMART_QUOTE(char_t, '{');
continue;
}
const auto argStart = pos;
static const std::string_view endChars = ":}";
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 std::string_view argName(argStart, pos);
std::string argFormat;
const string_view_t argName(argStart, pos);
string_view_t argFormat;
if (*pos == ':')
{
const auto formatStart = pos;
pos = std::find(pos, mFormat.end(), '}');
pos = std::find(pos, mFormat.end(), MIJIN_SMART_QUOTE(char_t, '}'));
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
argFormat = std::string_view(formatStart, pos);
argFormat = string_view_t(formatStart, pos);
}
// small utility that uses the provided string buffer for storing the format string
@@ -107,56 +153,102 @@ void SimpleLogFormatter<TAllocator>::format(const LogMessage& message, string_t&
// if there is no format, just directly print the value
if (argFormat.empty())
{
if constexpr (std::is_arithmetic_v<type_t>)
if constexpr (is_char_v<type_t>)
{
std::format_to(std::back_inserter(outFormatted), "{}", value);
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 = outFormatted.size();
outFormatted += '{';
outFormatted += argFormat;
outFormatted += '}';
const auto formatEnd = outFormatted.size();
auto format = std::string_view(outFormatted).substr(formatStart, formatEnd - formatStart);
const auto formatStart = mFormatBuffer.size();
mFormatBuffer += '{';
mFormatBuffer += argFormat;
mFormatBuffer += '}';
const auto formatEnd = mFormatBuffer.size();
// then append the formatted text
std::vformat_to(std::back_inserter(outFormatted), format, std::make_format_args(value));
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.");
}
};
// and then remove the format from the buffer again
outFormatted.erase(formatStart, formatEnd - formatStart);
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 == "text")
if (argName == MIJIN_SMART_QUOTE(char_t, "text"))
{
formatInline(message.text);
}
else if (argName == "channel")
else if (argName == MIJIN_SMART_QUOTE(char_t, "channel"))
{
formatInline(message.channel->name);
}
else if (argName == "file")
else if (argName == MIJIN_SMART_QUOTE(char_t, "file"))
{
formatInline(message.sourceLocation.file_name());
}
else if (argName == "function")
else if (argName == MIJIN_SMART_QUOTE(char_t, "function"))
{
formatInline(message.sourceLocation.function_name());
}
else if (argName == "line")
else if (argName == MIJIN_SMART_QUOTE(char_t, "line"))
{
formatInline(message.sourceLocation.line());
}
else if (argName == "column")
else if (argName == MIJIN_SMART_QUOTE(char_t, "column"))
{
formatInline(message.sourceLocation.column());
}
else if (argName == "level")
else if (argName == MIJIN_SMART_QUOTE(char_t, "level"))
{
formatInline(message.level->name);
}

View File

@@ -14,69 +14,139 @@
namespace mijin
{
struct LogLevel
#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
{
const char* name;
using char_t = TChar;
const char_t* name;
int value;
explicit operator int() const MIJIN_NOEXCEPT { return value; }
auto operator<=>(const LogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
auto operator<=>(const BaseLogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
};
struct LogChannel
MIJIN_DEFINE_CHAR_VERSIONS(LogLevel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogChannel
{
const char* name;
using char_t = TChar;
const char_t* name;
};
struct LogMessage
MIJIN_DEFINE_CHAR_VERSIONS(LogChannel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogMessage
{
const char* text;
const LogChannel* channel;
const LogLevel* level;
using char_t = TChar;
const char_t* text;
const BaseLogChannel<char_t>* channel;
const BaseLogLevel<char_t>* level;
std::source_location sourceLocation;
};
class LogSink
MIJIN_DEFINE_CHAR_VERSIONS(LogMessage)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogSink
{
public:
virtual ~LogSink() noexcept = default;
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual void handleMessage(const LogMessage& message) MIJIN_NOEXCEPT = 0;
virtual ~BaseLogSink() noexcept = default;
virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0;
};
template<template<typename T> typename TAllocator = std::allocator>
class Logger
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
{
private:
std::vector<LogSink*, TAllocator<LogSink*>> mSinks;
public:
explicit Logger(TAllocator<LogSink*> allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<LogSink*>>)
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))
{}
Logger(const Logger&) = default;
BaseLogger(const BaseLogger&) = default;
Logger(Logger&&) = default;
BaseLogger(BaseLogger&&) = default;
Logger& operator=(const Logger&) = default;
BaseLogger& operator=(const BaseLogger&) = default;
Logger& operator=(Logger&&) = default;
BaseLogger& operator=(BaseLogger&&) = default;
void addSink(LogSink& sink)
void addSink(sink_t& sink)
{
mSinks.push_back(&sink);
}
void postMessage(const LogMessage& message) const MIJIN_NOEXCEPT
void postMessage(const message_t& message) const MIJIN_NOEXCEPT
{
for (LogSink* sink: mSinks)
for (sink_t* sink: mSinks)
{
sink->handleMessage(message);
}
}
void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation, const char* msg) const MIJIN_NOEXCEPT
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, const char_t* msg) const MIJIN_NOEXCEPT
{
postMessage({
.text = msg,
@@ -87,87 +157,100 @@ public:
}
template<typename... TArgs>
void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation,
std::format_string<TArgs...> fmt, TArgs&& ... args) const
MIJIN_NOEXCEPT_IF(noexcept(std::declval<TAllocator<char>>().allocate(1)))
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_IF(noexcept(std::declval<allocator_t>().allocate(1)))
{
std::basic_string<char, std::char_traits<char>, TAllocator<char>> buffer(TAllocator<char>(mSinks.get_allocator()));
string_t buffer(allocator_t(mSinks.get_allocator()));
std::format_to(std::back_inserter(buffer), fmt, std::forward<TArgs>(args)...);
log(level, channel, std::move(sourceLocation), buffer.c_str());
}
};
#define MIJIN_DECLARE_LOG_CHANNEL(cnlName) \
namespace mijin_log_channel \
{ \
extern const ::mijin::LogChannel cnlName; \
}
#define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
#define MIJIN_DEFINE_LOG_CHANNEL(cnlName) \
namespace mijin_log_channel \
{ \
const ::mijin::LogChannel cnlName { \
.name = #cnlName \
}; \
}
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(Logger, LOGGER_COMMON_ARGS, LOGGER_SET_ARGS)
#define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) \
namespace mijin_log_level \
#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 \
{ \
inline constexpr ::mijin::LogLevel lvlName{ \
.name = #lvlName, \
.value = lvlValue \
}; \
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)
MIJIN_DECLARE_LOG_CHANNEL(GENERAL)
MIJIN_DEFINE_LOG_LEVEL(DEBUG, -1000)
MIJIN_DEFINE_LOG_LEVEL(VERBOSE, -500)
MIJIN_DEFINE_LOG_LEVEL(INFO, 0)
MIJIN_DEFINE_LOG_LEVEL(WARNING, 500)
MIJIN_DEFINE_LOG_LEVEL(ERROR, 1000)
#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)
inline constexpr int MIN_LOG_LEVEL = static_cast<int>(MIJIN_MIN_LOGLEVEL);
#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 = mijin_log_level::DEBUG.value;
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_DEBUG;
#else
inline constexpr int MIN_LOG_LEVEL = mijin_log_level::VERBOSE.value;
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_VERBOSE;
#endif
#define MIJIN_IMPORT_LOG_DEFAULTS \
namespace mijin_log_channel \
{ \
using ::mijin::mijin_log_channel::GENERAL; \
} \
\
namespace mijin_log_level \
{ \
using ::mijin::mijin_log_level::DEBUG; \
using ::mijin::mijin_log_level::VERBOSE; \
using ::mijin::mijin_log_level::INFO; \
using ::mijin::mijin_log_level::WARNING; \
using ::mijin::mijin_log_level::ERROR; \
}
#define MIJIN_DEFINE_DEFAULT_LOG_EVELS \
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_ALWAYS(level, channel, ...) mijin__getLogger__().log( \
mijin_log_level::level, mijin_log_channel::channel, std::source_location::current(), __VA_ARGS__ \
#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::level.value < mijin::MIN_LOG_LEVEL) {} \
#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__getLogger__() const noexcept \
{ \
return loggerExpr; \
#define MIJIN_SET_CLASS_LOGGER(loggerExpr) \
const auto& MIJIN_FUNCNAME_GET_LOGGER() const noexcept \
{ \
return loggerExpr; \
}
#define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \
auto mijin__getLogger__ = [&]() -> const auto& \
{ \
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)

View File

@@ -5,32 +5,50 @@
#define MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../util/traits.hpp"
namespace mijin
{
template<template<typename> typename TAllocator = std::allocator,
deleter_type<LogFormatter<TAllocator<char>>> TDeleter = AllocatorDeleter<TAllocator<LogFormatter<TAllocator<char>>>>>
requires(allocator_type<TAllocator<char>>)
class StdioSink : public FormattingLogSink<TAllocator, TDeleter>
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class StdioSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
{
public:
using base_t = FormattingLogSink<TAllocator, TDeleter>;
using base_t = BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>;
using typename base_t::char_t;
using typename base_t::allocator_t;
using typename base_t::formatter_ptr_t;
using typename base_t::message_t;
private:
int mMinStderrLevel;
public:
explicit StdioSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {},
int minStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING)
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)), mMinStderrLevel(minStderrLevel) {}
explicit StdioSink(not_null_t<formatter_ptr_t> formatter, TAllocator<char> allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<char>>)
: base_t(std::move(formatter), std::move(allocator)) {}
void handleMessageFormatted(const LogMessage& message, const char* formatted) MIJIN_NOEXCEPT override
void handleMessageFormatted(const message_t& message, const char_t* formatted) MIJIN_NOEXCEPT override
{
if (*message.level >= mijin_log_level::WARNING)
FILE* stream = (message.level->value >= mMinStderrLevel) ? stderr : stdout;
if constexpr (std::is_same_v<char_t, char>)
{
std::fputs(formatted, stderr);
std::fputc('\n', stderr);
std::fputs(formatted, stream);
std::fputc('\n', stream);
}
else if constexpr (std::is_same_v<char_t, wchar_t>)
{
std::fputws(formatted, stream);
std::fputwc(L'\n', stream);
}
else if constexpr (sizeof(char_t) == sizeof(char))
{
// char8_t etc.
std::fputs(std::bit_cast<const char*>(formatted), stream);
std::fputc('\n', stream);
}
else
{
std::puts(formatted);
static_assert(always_false_v<char_t>, "Character type not supported.");
}
}
};