From 73820d4844ff779d0a8f469e8dd4f98f90006b02 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Fri, 21 Jan 2022 12:09:10 +0300 Subject: [PATCH] Support custom sinks in logging --- libs/app/include/psemek/app/main.hpp | 2 +- libs/log/include/psemek/log/log.hpp | 21 ++++- libs/log/source/log.cpp | 119 +++++++++++++++++++-------- 3 files changed, 104 insertions(+), 38 deletions(-) diff --git a/libs/app/include/psemek/app/main.hpp b/libs/app/include/psemek/app/main.hpp index cf49ecf7..0bb477cc 100644 --- a/libs/app/include/psemek/app/main.hpp +++ b/libs/app/include/psemek/app/main.hpp @@ -10,7 +10,7 @@ namespace psemek::app template int main(Args && ... args) try { - log::add_sink(io::std_out(), log::level::debug); + log::add_sink(log::default_sink(io::std_out(), log::level::debug)); log::register_thread("main"); App app(std::forward(args)...); diff --git a/libs/log/include/psemek/log/log.hpp b/libs/log/include/psemek/log/log.hpp index a84671eb..52aa444e 100644 --- a/libs/log/include/psemek/log/log.hpp +++ b/libs/log/include/psemek/log/log.hpp @@ -35,7 +35,26 @@ namespace psemek::log std::thread::id id_; }; - void add_sink(std::unique_ptr stream, level l); + using clock = std::chrono::system_clock; + + struct message + { + clock::time_point time; + std::string_view thread_name; + enum level level; + std::string_view message; + }; + + struct sink + { + virtual void put_message(message const & msg) = 0; + virtual ~sink() {} + }; + + std::unique_ptr default_sink(std::unique_ptr stream, level l); + + sink * add_sink(std::unique_ptr s); + std::unique_ptr remove_sink(sink * s); void put_message(level l, std::string const & message); diff --git a/libs/log/source/log.cpp b/libs/log/source/log.cpp index 71a6d9df..ce166258 100644 --- a/libs/log/source/log.cpp +++ b/libs/log/source/log.cpp @@ -14,15 +14,74 @@ namespace psemek::log { - static std::atomic max_thread_name_length = 3; + namespace + { - static std::mutex thread_names_mutex; - static std::unordered_map thread_names; + std::atomic max_thread_name_length = 3; - static std::mutex sinks_mutex; - static std::vector, level>> sinks; + std::mutex thread_names_mutex; + std::unordered_map thread_names; - static void put_message(level l, std::string const & message, std::string const & thread_name); + std::mutex sinks_mutex; + std::vector> sinks; + + void put_message(level l, std::string const & str, std::string const & thread_name) + { + message msg + { + clock::now(), + thread_name, + l, + str + }; + + for (auto const & sink : sinks) + { + sink->put_message(msg); + } + } + + static ::tm safe_localtime(std::time_t const & time) + { + static std::mutex mutex; + std::lock_guard lock{mutex}; + return *std::localtime(&time); + } + + struct default_sink_impl + : sink + { + default_sink_impl(std::unique_ptr stream, level l) + : stream_(std::move(stream)) + , level_(l) + {} + + void put_message(message const & msg) override + { + if (msg.level < level_) return; + + auto const time = clock::to_time_t(msg.time); + auto const tm = safe_localtime(time); + + auto const millis = std::chrono::duration_cast(msg.time.time_since_epoch()).count() % 1000; + + std::ostringstream os; + os + << '[' << std::put_time(&tm, "%Y %b %d %H:%M:%S.") << std::setw(3) << std::setfill('0') << millis << ']' + << '[' << std::setw(max_thread_name_length) << std::setfill(' ') << msg.thread_name << ']' + << '[' << std::setw(5) << msg.level << ']' + << ' ' << msg.message << '\n'; + + auto const str = os.str(); + stream_->write(str.data(), str.size()); + } + + private: + std::unique_ptr stream_; + level level_; + }; + + } void register_thread(std::string name) { @@ -70,44 +129,32 @@ namespace psemek::log put_message(level::info, "Thread \"" + name + "\" unregistered"); } - static ::tm safe_localtime(std::time_t const & time) + std::unique_ptr default_sink(std::unique_ptr stream, level l) { - static std::mutex mutex; - std::lock_guard lock{mutex}; - return *std::localtime(&time); + return std::make_unique(std::move(stream), l); } - static void put_message(level l, std::string const & message, std::string const & thread_name) + sink * add_sink(std::unique_ptr s) { - using clock = std::chrono::system_clock; - - auto const time = clock::to_time_t(clock::now()); - auto const tm = safe_localtime(time); - - auto const millis = std::chrono::duration_cast(clock::now().time_since_epoch()).count() % 1000; - - std::ostringstream os; - os - << '[' << std::put_time(&tm, "%Y %b %d %H:%M:%S.") << std::setw(3) << std::setfill('0') << millis << ']' - << '[' << std::setw(max_thread_name_length) << std::setfill(' ') << thread_name << ']' - << '[' << std::setw(5) << l << ']' - << ' ' << message << '\n'; - - auto const str = os.str(); - + auto ptr = s.get(); std::lock_guard lock{sinks_mutex}; + sinks.push_back(std::move(s)); + return ptr; + } - for (auto const & sink : sinks) + std::unique_ptr remove_sink(sink * stream) + { + std::lock_guard lock{sinks_mutex}; + for (std::size_t i = 0; i < sinks.size(); ++i) { - if (static_cast(l) >= static_cast(sink.second)) - sink.first->write(str.data(), str.size()); + if (sinks[i].get() == stream) + { + auto result = std::move(sinks[i]); + sinks.erase(sinks.begin() + i); + return result; + } } - } - - void add_sink(std::unique_ptr stream, level l) - { - std::lock_guard lock{sinks_mutex}; - sinks.push_back({std::move(stream), l}); + return nullptr; } void put_message(level l, std::string const & message)