psemek/libs/log/source/log.cpp

132 lines
3.3 KiB
C++

#include <psemek/log/log.hpp>
#include <chrono>
#include <ctime>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <mutex>
#include <unordered_map>
#include <thread>
#include <atomic>
#include <vector>
namespace psemek::log
{
static std::atomic<std::size_t> max_thread_name_length = 3;
static std::mutex thread_names_mutex;
static std::unordered_map<std::thread::id, std::string> thread_names;
static std::mutex sinks_mutex;
static std::vector<std::pair<std::unique_ptr<io::ostream>, 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<std::chrono::milliseconds>(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<int>(l) >= static_cast<int>(sink.second))
sink.first->write(str.data(), str.size());
}
}
void add_sink(std::unique_ptr<io::ostream> 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);
}
}