#include #include #include #include #include #include #include #include #include #include #include namespace psemek::log { namespace { std::atomic max_thread_name_length = 3; std::mutex thread_names_mutex; std::unordered_map thread_names; 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) { register_thread(std::this_thread::get_id(), std::move(name)); } void register_thread(std::thread::id id, std::string name) { { std::lock_guard lock{thread_names_mutex}; auto it = thread_names.find(id); if (it != thread_names.end()) throw std::runtime_error("Thread \"" + name + "\" already registered!"); thread_names[id] = name; } max_thread_name_length = std::max(max_thread_name_length.load(), name.size()); put_message(level::info, "Thread \"" + name + "\" registered"); } void unregister_thread(std::thread::id id) { std::string name; { std::lock_guard lock{thread_names_mutex}; auto it = thread_names.find(id); if (it == thread_names.end()) { std::ostringstream os; os << "Thread " << id << " not found!"; throw std::runtime_error(os.str()); } name = std::move(it->second); thread_names.erase(it); } if (id == std::this_thread::get_id()) put_message(level::info, "Thread \"" + name + "\" unregistered", name); else put_message(level::info, "Thread \"" + name + "\" unregistered"); } std::unique_ptr default_sink(std::unique_ptr stream, level l) { return std::make_unique(std::move(stream), l); } sink * add_sink(std::unique_ptr s) { auto ptr = s.get(); std::lock_guard lock{sinks_mutex}; sinks.push_back(std::move(s)); return ptr; } std::unique_ptr remove_sink(sink * stream) { std::lock_guard lock{sinks_mutex}; for (std::size_t i = 0; i < sinks.size(); ++i) { if (sinks[i].get() == stream) { auto result = std::move(sinks[i]); sinks.erase(sinks.begin() + i); return result; } } return nullptr; } void put_message(level l, std::string const & message) { static std::string unknown_thread_name = "???"; auto const id = std::this_thread::get_id(); std::string const * thread_name = nullptr; { std::lock_guard lock{thread_names_mutex}; auto const it = thread_names.find(id); if (it == thread_names.end()) thread_name = &unknown_thread_name; else thread_name = &(it->second); } put_message(l, message, *thread_name); } }