Made logger thread-safe and added filters.

This commit is contained in:
Patrick Wuttke 2025-07-14 17:16:24 +02:00
parent ad627b7c70
commit 02e99bbc82
3 changed files with 149 additions and 6 deletions

View File

@ -0,0 +1,52 @@
#pragma once
#if !defined(MIJIN_LOGGING_FILTERS_HPP_INCLUDED)
#define MIJIN_LOGGING_FILTERS_HPP_INCLUDED 1
#include "./logger.hpp"
namespace mijin
{
template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE>
class BaseLevelFilter : public BaseLogFilter<TChar>
{
public:
using base_t = BaseLogFilter<TChar>;
using typename base_t::char_t;
using typename base_t::message_t;
private:
int mMinLevel = 0;
int mMaxLevel = 0;
public:
explicit BaseLevelFilter(int minLevel, int maxLevel = std::numeric_limits<int>::max()) MIJIN_NOEXCEPT
: mMinLevel(minLevel), mMaxLevel(maxLevel) {}
explicit BaseLevelFilter(const BaseLogLevel<char_t>& minLevel, const BaseLogLevel<char_t>& maxLevel = {nullptr, std::numeric_limits<int>::max()}) MIJIN_NOEXCEPT
: mMinLevel(minLevel.value), mMaxLevel(maxLevel.value) {}
[[nodiscard]]
int getMinLevel() const MIJIN_NOEXCEPT { return mMinLevel; }
[[nodiscard]]
int getMaxLevel() const MIJIN_NOEXCEPT { return mMaxLevel; }
void setMinLevel(int level) MIJIN_NOEXCEPT { mMinLevel = level; }
void setMinLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMinLevel = level.value; }
void setMaxLevel(int level) MIJIN_NOEXCEPT { mMaxLevel = level; }
void setMaxLevel(const BaseLogLevel<char_t>& level) MIJIN_NOEXCEPT { mMaxLevel = level.value; }
bool shouldShow(const message_t& message) MIJIN_NOEXCEPT override
{
return message.level->value >= mMinLevel && message.level->value <= mMaxLevel;
}
};
MIJIN_DEFINE_CHAR_VERSIONS(LevelFilter)
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)

View File

@ -6,6 +6,7 @@
#include <cstdint> #include <cstdint>
#include <format> #include <format>
#include <mutex>
#include <source_location> #include <source_location>
#include <string> #include <string>
#include <vector> #include <vector>
@ -104,6 +105,20 @@ public:
MIJIN_DEFINE_CHAR_VERSIONS(LogSink) 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 #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)> template<typename TChar = MIJIN_DEFAULT_CHAR_TYPE, typename TTraits = std::char_traits<TChar>, LOGGER_COMMON_ARGS(TChar)>
class BaseLogger class BaseLogger
@ -114,15 +129,22 @@ public:
using allocator_t = TAllocator<char_t>; using allocator_t = TAllocator<char_t>;
using sink_t = BaseLogSink<char_t>; using sink_t = BaseLogSink<char_t>;
using filter_t = BaseLogFilter<char_t>;
using level_t = BaseLogLevel<char_t>; using level_t = BaseLogLevel<char_t>;
using channel_t = BaseLogChannel<char_t>; using channel_t = BaseLogChannel<char_t>;
using message_t = BaseLogMessage<char_t>; using message_t = BaseLogMessage<char_t>;
using string_t = std::basic_string<char_t, traits_t, allocator_t>; using string_t = std::basic_string<char_t, traits_t, allocator_t>;
private: private:
std::vector<sink_t*, TAllocator<sink_t*>> mSinks; struct SinkEntry
{
sink_t* sink;
filter_t* filter;
};
std::vector<SinkEntry, TAllocator<SinkEntry>> mSinks;
mutable std::mutex mMutex;
public: public:
explicit BaseLogger(TAllocator<sink_t*> allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<sink_t*>>) explicit BaseLogger(TAllocator<void> allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator<SinkEntry>, TAllocator<void>&&>))
: mSinks(std::move(allocator)) : mSinks(TAllocator<SinkEntry>(std::move(allocator)))
{} {}
BaseLogger(const BaseLogger&) = default; BaseLogger(const BaseLogger&) = default;
@ -135,14 +157,25 @@ public:
void addSink(sink_t& sink) void addSink(sink_t& sink)
{ {
mSinks.push_back(&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 void postMessage(const message_t& message) const MIJIN_NOEXCEPT
{ {
for (sink_t* sink: mSinks) std::unique_lock _(mMutex);
for (const SinkEntry& entry : mSinks)
{ {
sink->handleMessage(message); if (entry.filter != nullptr && !entry.filter->shouldShow(message)) {
continue;
}
entry.sink->handleMessage(message);
} }
} }

View File

@ -0,0 +1,58 @@
#pragma once
#if !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)
#define MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED 1
#include "./formatting.hpp"
#include "../io/stream.hpp"
#include "../util/traits.hpp"
namespace mijin
{
template<MIJIN_FORMATTING_SINK_TMPL_ARGS_INIT>
requires(allocator_type<TAllocator<TChar>>)
class BaseStreamSink : public BaseFormattingLogSink<MIJIN_FORMATTING_SINK_TMP_ARG_NAMES>
{
public:
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;
using stream_ptr_t = DynamicPointer<Stream>;
private:
stream_ptr_t mStream;
int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
public:
explicit BaseStreamSink(not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)) {}
explicit BaseStreamSink(not_null_t<stream_ptr_t> stream, not_null_t<formatter_ptr_t> formatter, allocator_t allocator = {})
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<allocator_t>)
: base_t(std::move(formatter), std::move(allocator)), mStream(std::move(stream)) {}
void setStream(not_null_t<stream_ptr_t> stream) {
mStream = std::move(stream).release();
}
void handleMessageFormatted(const message_t& /* message */, const char_t* formatted) MIJIN_NOEXCEPT override
{
if (!mStream) {
return;
}
(void) mStream->writeSpan(std::basic_string_view(formatted));
(void) mStream->write('\n');
mStream->flush();
}
};
#define SINK_SET_ARGS(chr_type) chr_type, std::char_traits<chr_type>, TAllocator, TDeleter
MIJIN_DEFINE_CHAR_VERSIONS_TMPL(StreamSink, MIJIN_FORMATTING_SINK_COMMON_ARGS, SINK_SET_ARGS)
#undef SINK_SET_ARGS
} // namespace mijin
#endif // !defined(MIJIN_LOGGING_STREAM_SINK_HPP_INCLUDED)