From 4bd6843ba58216081cca54157494eed97823b397 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 cf528fa..5aea0a8 100644 --- a/source/mijin/logging/logger.hpp +++ b/source/mijin/logging/logger.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -105,6 +106,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 @@ -115,15 +130,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; @@ -133,14 +155,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)