From 02e99bbc824170fe3256c6edb95c646eac7375fe Mon Sep 17 00:00:00 2001
From: Patrick Wuttke
Date: Mon, 14 Jul 2025 17:16:24 +0200
Subject: [PATCH] Made logger thread-safe and added filters.
---
source/mijin/logging/filters.hpp | 52 +++++++++++++++++++++++++
source/mijin/logging/logger.hpp | 45 ++++++++++++++++++---
source/mijin/logging/stream_sink.hpp | 58 ++++++++++++++++++++++++++++
3 files changed, 149 insertions(+), 6 deletions(-)
create mode 100644 source/mijin/logging/filters.hpp
create mode 100644 source/mijin/logging/stream_sink.hpp
diff --git a/source/mijin/logging/filters.hpp b/source/mijin/logging/filters.hpp
new file mode 100644
index 0000000..0f2b2b3
--- /dev/null
+++ b/source/mijin/logging/filters.hpp
@@ -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
+class BaseLevelFilter : public BaseLogFilter
+{
+public:
+ using base_t = BaseLogFilter;
+ 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::max()) MIJIN_NOEXCEPT
+ : mMinLevel(minLevel), mMaxLevel(maxLevel) {}
+ explicit BaseLevelFilter(const BaseLogLevel& minLevel, const BaseLogLevel& maxLevel = {nullptr, std::numeric_limits::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& level) MIJIN_NOEXCEPT { mMinLevel = level.value; }
+
+ void setMaxLevel(int level) MIJIN_NOEXCEPT { mMaxLevel = level; }
+
+ void setMaxLevel(const BaseLogLevel& 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)
diff --git a/source/mijin/logging/logger.hpp b/source/mijin/logging/logger.hpp
index d2db264..4904ae7 100644
--- a/source/mijin/logging/logger.hpp
+++ b/source/mijin/logging/logger.hpp
@@ -6,6 +6,7 @@
#include
#include
+#include
#include
#include
#include
@@ -104,6 +105,20 @@ public:
MIJIN_DEFINE_CHAR_VERSIONS(LogSink)
+template
+class BaseLogFilter
+{
+public:
+ using char_t = TChar;
+ using message_t = BaseLogMessage;
+
+ 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 TAllocator = MIJIN_DEFAULT_ALLOCATOR
template, LOGGER_COMMON_ARGS(TChar)>
class BaseLogger
@@ -114,15 +129,22 @@ public:
using allocator_t = TAllocator;
using sink_t = BaseLogSink;
+ using filter_t = BaseLogFilter;
using level_t = BaseLogLevel;
using channel_t = BaseLogChannel;
using message_t = BaseLogMessage;
using string_t = std::basic_string;
private:
- std::vector> mSinks;
+ struct SinkEntry
+ {
+ sink_t* sink;
+ filter_t* filter;
+ };
+ std::vector> mSinks;
+ mutable std::mutex mMutex;
public:
- explicit BaseLogger(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v>)
- : mSinks(std::move(allocator))
+ explicit BaseLogger(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v, TAllocator&&>))
+ : mSinks(TAllocator(std::move(allocator)))
{}
BaseLogger(const BaseLogger&) = default;
@@ -135,14 +157,25 @@ public:
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
{
- 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);
}
}
diff --git a/source/mijin/logging/stream_sink.hpp b/source/mijin/logging/stream_sink.hpp
new file mode 100644
index 0000000..9dd5523
--- /dev/null
+++ b/source/mijin/logging/stream_sink.hpp
@@ -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
+ requires(allocator_type>)
+class BaseStreamSink : public BaseFormattingLogSink
+{
+public:
+ using base_t = BaseFormattingLogSink;
+ 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;
+private:
+ stream_ptr_t mStream;
+ int mMinStderrLevel = MIJIN_LOG_LEVEL_VALUE_WARNING;
+public:
+ explicit BaseStreamSink(not_null_t formatter, allocator_t allocator = {})
+ MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v)
+ : base_t(std::move(formatter), std::move(allocator)) {}
+ explicit BaseStreamSink(not_null_t stream, not_null_t formatter, allocator_t allocator = {})
+ MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v)
+ : base_t(std::move(formatter), std::move(allocator)), mStream(std::move(stream)) {}
+
+ void setStream(not_null_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, 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)