Added more stuff like Logging, DynamicPointer, utilities for using STL allocators and NotNullable.
This commit is contained in:
176
source/mijin/logging/formatting.hpp
Normal file
176
source/mijin/logging/formatting.hpp
Normal file
@@ -0,0 +1,176 @@
|
||||
|
||||
#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)
|
||||
13
source/mijin/logging/logger.cpp
Normal file
13
source/mijin/logging/logger.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
#include "logger.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
MIJIN_DEFINE_LOG_CHANNEL(GENERAL)
|
||||
|
||||
} // namespace mijin
|
||||
173
source/mijin/logging/logger.hpp
Normal file
173
source/mijin/logging/logger.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/annot.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
struct LogLevel
|
||||
{
|
||||
const char* name;
|
||||
int value;
|
||||
|
||||
explicit operator int() const MIJIN_NOEXCEPT { return value; }
|
||||
|
||||
auto operator<=>(const LogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
|
||||
};
|
||||
|
||||
struct LogChannel
|
||||
{
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct LogMessage
|
||||
{
|
||||
const char* text;
|
||||
const LogChannel* channel;
|
||||
const LogLevel* level;
|
||||
std::source_location sourceLocation;
|
||||
};
|
||||
|
||||
class LogSink
|
||||
{
|
||||
public:
|
||||
virtual ~LogSink() noexcept = default;
|
||||
|
||||
virtual void handleMessage(const LogMessage& message) MIJIN_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
template<template<typename T> typename TAllocator = std::allocator>
|
||||
class Logger
|
||||
{
|
||||
private:
|
||||
std::vector<LogSink*, TAllocator<LogSink*>> mSinks;
|
||||
public:
|
||||
explicit Logger(TAllocator<LogSink*> allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<LogSink*>>)
|
||||
: mSinks(std::move(allocator))
|
||||
{}
|
||||
|
||||
Logger(const Logger&) = default;
|
||||
|
||||
Logger(Logger&&) = default;
|
||||
|
||||
Logger& operator=(const Logger&) = default;
|
||||
|
||||
Logger& operator=(Logger&&) = default;
|
||||
|
||||
void addSink(LogSink& sink)
|
||||
{
|
||||
mSinks.push_back(&sink);
|
||||
}
|
||||
|
||||
void postMessage(const LogMessage& message) const MIJIN_NOEXCEPT
|
||||
{
|
||||
for (LogSink* sink: mSinks)
|
||||
{
|
||||
sink->handleMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation, const char* msg) const MIJIN_NOEXCEPT
|
||||
{
|
||||
postMessage({
|
||||
.text = msg,
|
||||
.channel = &channel,
|
||||
.level = &level,
|
||||
.sourceLocation = std::move(sourceLocation)
|
||||
});
|
||||
}
|
||||
|
||||
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)))
|
||||
{
|
||||
std::basic_string<char, std::char_traits<char>, TAllocator<char>> buffer(TAllocator<char>(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 MIJIN_DEFINE_LOG_CHANNEL(cnlName) \
|
||||
namespace mijin_log_channel \
|
||||
{ \
|
||||
const ::mijin::LogChannel cnlName { \
|
||||
.name = #cnlName \
|
||||
}; \
|
||||
}
|
||||
|
||||
#define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) \
|
||||
namespace mijin_log_level \
|
||||
{ \
|
||||
inline constexpr ::mijin::LogLevel lvlName{ \
|
||||
.name = #lvlName, \
|
||||
.value = lvlValue \
|
||||
}; \
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
#if defined(MIJIN_MIN_LOGLEVEL)
|
||||
inline constexpr int MIN_LOG_LEVEL = static_cast<int>(MIJIN_MIN_LOGLEVEL);
|
||||
#elif defined(MIJIN_DEBUG)
|
||||
inline constexpr int MIN_LOG_LEVEL = mijin_log_level::DEBUG.value;
|
||||
#else
|
||||
inline constexpr int MIN_LOG_LEVEL = mijin_log_level::VERBOSE.value;
|
||||
#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_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, channel, ...) \
|
||||
if constexpr (mijin_log_level::level.value < mijin::MIN_LOG_LEVEL) {} \
|
||||
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_SCOPE_LOGGER(loggerExpr) \
|
||||
auto mijin__getLogger__ = [&]() -> const auto& \
|
||||
{ \
|
||||
return loggerExpr; \
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
|
||||
40
source/mijin/logging/stdio_sink.hpp
Normal file
40
source/mijin/logging/stdio_sink.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED 1
|
||||
|
||||
#include "./formatting.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>
|
||||
{
|
||||
public:
|
||||
using base_t = FormattingLogSink<TAllocator, TDeleter>;
|
||||
using typename base_t::formatter_ptr_t;
|
||||
|
||||
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
|
||||
{
|
||||
if (*message.level >= mijin_log_level::WARNING)
|
||||
{
|
||||
std::fputs(formatted, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::puts(formatted);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
|
||||
Reference in New Issue
Block a user