238 lines
5 KiB
C++
238 lines
5 KiB
C++
#include <psemek/log/log.hpp>
|
|
#include <psemek/util/to_string.hpp>
|
|
#include <psemek/util/exception.hpp>
|
|
|
|
#include <chrono>
|
|
#include <ctime>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <thread>
|
|
#include <atomic>
|
|
#include <vector>
|
|
#include <csignal>
|
|
|
|
namespace psemek::log
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
std::atomic<std::size_t> max_thread_name_length = 3;
|
|
|
|
std::mutex thread_names_mutex;
|
|
std::unordered_map<std::thread::id, std::string> thread_names;
|
|
|
|
std::mutex sinks_mutex;
|
|
std::vector<std::unique_ptr<sink>> sinks;
|
|
|
|
using signal_handler = void(*)(int);
|
|
|
|
struct signal_data
|
|
{
|
|
int signal;
|
|
std::string message;
|
|
};
|
|
|
|
signal_data const signals[] =
|
|
{
|
|
{SIGSEGV, "Aborting due to SIGSEGV"},
|
|
{SIGABRT, "Aborting due to SIGABRT"},
|
|
{SIGTERM, "Aborting due to SIGTERM"},
|
|
};
|
|
|
|
struct sinks_flusher
|
|
{
|
|
sinks_flusher()
|
|
{
|
|
for (auto const & p : signals)
|
|
std::signal(p.signal, &handler);
|
|
}
|
|
|
|
private:
|
|
static void handler(int signal)
|
|
{
|
|
for (auto const & p : signals)
|
|
{
|
|
if (p.signal == signal)
|
|
{
|
|
put_message(level::error, p.message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
std::lock_guard lock{sinks_mutex};
|
|
for (auto & sink : sinks)
|
|
sink->flush();
|
|
sinks.clear();
|
|
}
|
|
|
|
std::signal(signal, SIG_DFL);
|
|
std::raise(signal);
|
|
}
|
|
} flusher;
|
|
|
|
void put_message(level l, std::string const & str, std::string const & thread_name)
|
|
{
|
|
message msg
|
|
{
|
|
clock::now(),
|
|
thread_name,
|
|
l,
|
|
str
|
|
};
|
|
|
|
std::lock_guard lock{sinks_mutex};
|
|
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<io::ostream> stream, level min, level max)
|
|
: stream_(std::move(stream))
|
|
, min_(min)
|
|
, max_(max)
|
|
{}
|
|
|
|
void put_message(message const & msg) override
|
|
{
|
|
if (msg.level < min_ || msg.level > max_) return;
|
|
|
|
auto const time = clock::to_time_t(msg.time);
|
|
auto const tm = safe_localtime(time);
|
|
|
|
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(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());
|
|
}
|
|
|
|
void flush() override
|
|
{
|
|
stream_->flush();
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<io::ostream> stream_;
|
|
level min_;
|
|
level max_;
|
|
};
|
|
|
|
}
|
|
|
|
void register_thread(std::string name)
|
|
{
|
|
#ifdef _PTHREAD_H
|
|
#ifdef _GNU_SOURCE
|
|
::pthread_setname_np(::pthread_self(), name.data());
|
|
#endif
|
|
#endif
|
|
|
|
auto id = std::this_thread::get_id();
|
|
|
|
{
|
|
std::lock_guard lock{thread_names_mutex};
|
|
|
|
auto it = thread_names.find(id);
|
|
if (it != thread_names.end())
|
|
throw util::exception("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 util::exception(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<sink> default_sink(std::unique_ptr<io::ostream> stream, level min, level max)
|
|
{
|
|
return std::make_unique<default_sink_impl>(std::move(stream), min, max);
|
|
}
|
|
|
|
sink * add_sink(std::unique_ptr<sink> s)
|
|
{
|
|
auto ptr = s.get();
|
|
std::lock_guard lock{sinks_mutex};
|
|
sinks.push_back(std::move(s));
|
|
return ptr;
|
|
}
|
|
|
|
std::unique_ptr<sink> 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);
|
|
}
|
|
|
|
}
|