mijin2/source/mijin/logging/logger.hpp
2025-07-14 19:09:55 +02:00

304 lines
10 KiB
C++

#pragma once
#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1
#include <cstdint>
#include <format>
#include <mutex>
#include <source_location>
#include <string>
#include <vector>
#include "../internal/common.hpp"
#include "../util/annot.hpp"
#include "../util/iterators.hpp"
namespace mijin
{
#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
{
using char_t = TChar;
const char_t* name;
int value;
explicit operator int() const MIJIN_NOEXCEPT { return value; }
auto operator<=>(const BaseLogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
};
MIJIN_DEFINE_CHAR_VERSIONS(LogLevel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogChannel
{
using char_t = TChar;
const char_t* name;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogChannel)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
struct BaseLogMessage
{
using char_t = TChar;
const char_t* text;
const BaseLogChannel<char_t>* channel;
const BaseLogLevel<char_t>* level;
std::source_location sourceLocation;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogMessage)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogSink
{
public:
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual ~BaseLogSink() noexcept = default;
virtual void handleMessage(const message_t& message) MIJIN_NOEXCEPT = 0;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogSink)
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLogFilter
{
public:
using char_t = TChar;
using message_t = BaseLogMessage<char_t>;
virtual ~BaseLogFilter() noexcept = default;
virtual bool shouldShow(const message_t& message) MIJIN_NOEXCEPT = 0;
};
MIJIN_DEFINE_CHAR_VERSIONS(LogFilter)
#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
{
public:
using char_t = TChar;
using traits_t = TTraits;
using allocator_t = TAllocator<char_t>;
using sink_t = BaseLogSink<char_t>;
using filter_t = BaseLogFilter<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:
struct SinkEntry
{
sink_t* sink;
filter_t* filter;
};
std::vector<SinkEntry, TAllocator<SinkEntry>> mSinks;
mutable std::mutex mMutex;
public:
explicit BaseLogger(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<SinkEntry>, TAllocator<void>&&>))
: mSinks(TAllocator<SinkEntry>(std::move(allocator)))
{}
BaseLogger(const BaseLogger&) = default;
BaseLogger(BaseLogger&&) = default;
BaseLogger& operator=(const BaseLogger&) = default;
BaseLogger& operator=(BaseLogger&&) = default;
void addSink(sink_t& sink)
{
std::unique_lock _(mMutex);
mSinks.push_back({&sink, nullptr});
}
void addSink(sink_t& sink, filter_t& filter)
{
std::unique_lock _(mMutex);
mSinks.push_back({&sink, &filter});
}
void postMessage(const message_t& message) const MIJIN_NOEXCEPT
{
std::unique_lock _(mMutex);
for (const SinkEntry& entry : mSinks)
{
if (entry.filter != nullptr && !entry.filter->shouldShow(message)) {
continue;
}
entry.sink->handleMessage(message);
}
}
void log(const level_t& level, const channel_t& channel, std::source_location sourceLocation, const char_t* msg) const MIJIN_NOEXCEPT
{
postMessage({
.text = msg,
.channel = &channel,
.level = &level,
.sourceLocation = std::move(sourceLocation)
});
}
template<typename... TArgs>
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
{
// TODO: make the logger use a traits struct to make this adjustable
static constexpr std::size_t BUFFER_SIZE = 256;
std::array<char_t, BUFFER_SIZE> buffer;
// first try to write into a buffer on the stack
FixedArrayOutputIterator itAfter = std::format_to(FixedArrayOutputIterator(buffer), fmt, std::forward<TArgs>(args)...);
*itAfter = '\0';
++itAfter;
if (!itAfter.didOverflow())
{
log(level, channel, std::move(sourceLocation), buffer.data());
return;
}
// if that didn't work, allocate more space
const std::size_t newBufferSize = itAfter.getCounter();
char_t* newBuffer = static_cast<char_t*>(alloca(newBufferSize * sizeof(char_t)));
const std::format_to_n_result result = std::format_to_n(newBuffer, newBufferSize - 1, fmt, std::forward<TArgs>(args)...);
*result.out = '\0';
log(level, channel, std::move(sourceLocation), newBuffer);
}
};
#define LOGGER_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(Logger, LOGGER_COMMON_ARGS, LOGGER_SET_ARGS)
#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 \
{ \
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)
#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_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_COMPILE = MIJIN_LOG_LEVEL_VALUE_DEBUG;
#else
inline constexpr int MIN_LOG_LEVEL_COMPILE = MIJIN_LOG_LEVEL_VALUE_VERBOSE;
#endif
#define MIJIN_DEFINE_DEFAULT_LOG_LEVELS \
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_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_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_FUNCNAME_GET_LOGGER() const noexcept \
{ \
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)