#include #include #include #include #include #include #include #include #include #include #include namespace psemek::log { static std::atomic max_thread_name_length = 3; static std::mutex thread_names_mutex; static std::unordered_map thread_names; static std::mutex sinks_mutex; static std::vector, level>> sinks; static void put_message(level l, std::string const & message, std::string const & thread_name); 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"); } static ::tm safe_localtime(std::time_t const & time) { static std::mutex mutex; std::lock_guard lock{mutex}; return *std::localtime(&time); } static void put_message(level l, std::string const & message, std::string const & thread_name) { 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(7) << l << ']' << ' ' << message << '\n'; auto const str = os.str(); std::lock_guard lock{sinks_mutex}; for (auto const & sink : sinks) { if (static_cast(l) >= static_cast(sink.second)) sink.first->write(str.data(), str.size()); } } void add_sink(std::unique_ptr stream, level l) { std::lock_guard lock{sinks_mutex}; sinks.push_back({std::move(stream), l}); } 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); } }