304 lines
10 KiB
C++
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)
|