Support custom sinks in logging
This commit is contained in:
parent
88fa197f8c
commit
73820d4844
3 changed files with 104 additions and 38 deletions
|
|
@ -10,7 +10,7 @@ namespace psemek::app
|
||||||
template <typename App, typename ... Args>
|
template <typename App, typename ... Args>
|
||||||
int main(Args && ... args) try
|
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");
|
log::register_thread("main");
|
||||||
|
|
||||||
App app(std::forward<Args>(args)...);
|
App app(std::forward<Args>(args)...);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,26 @@ namespace psemek::log
|
||||||
std::thread::id id_;
|
std::thread::id id_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void add_sink(std::unique_ptr<io::ostream> 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<sink> default_sink(std::unique_ptr<io::ostream> stream, level l);
|
||||||
|
|
||||||
|
sink * add_sink(std::unique_ptr<sink> s);
|
||||||
|
std::unique_ptr<sink> remove_sink(sink * s);
|
||||||
|
|
||||||
void put_message(level l, std::string const & message);
|
void put_message(level l, std::string const & message);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,74 @@
|
||||||
namespace psemek::log
|
namespace psemek::log
|
||||||
{
|
{
|
||||||
|
|
||||||
static std::atomic<std::size_t> max_thread_name_length = 3;
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
static std::mutex thread_names_mutex;
|
std::atomic<std::size_t> max_thread_name_length = 3;
|
||||||
static std::unordered_map<std::thread::id, std::string> thread_names;
|
|
||||||
|
|
||||||
static std::mutex sinks_mutex;
|
std::mutex thread_names_mutex;
|
||||||
static std::vector<std::pair<std::unique_ptr<io::ostream>, level>> sinks;
|
std::unordered_map<std::thread::id, std::string> thread_names;
|
||||||
|
|
||||||
static void put_message(level l, std::string const & message, std::string const & thread_name);
|
std::mutex sinks_mutex;
|
||||||
|
std::vector<std::unique_ptr<sink>> 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<io::ostream> 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<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());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<io::ostream> stream_;
|
||||||
|
level level_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void register_thread(std::string name)
|
void register_thread(std::string name)
|
||||||
{
|
{
|
||||||
|
|
@ -70,44 +129,32 @@ namespace psemek::log
|
||||||
put_message(level::info, "Thread \"" + name + "\" unregistered");
|
put_message(level::info, "Thread \"" + name + "\" unregistered");
|
||||||
}
|
}
|
||||||
|
|
||||||
static ::tm safe_localtime(std::time_t const & time)
|
std::unique_ptr<sink> default_sink(std::unique_ptr<io::ostream> stream, level l)
|
||||||
{
|
{
|
||||||
static std::mutex mutex;
|
return std::make_unique<default_sink_impl>(std::move(stream), l);
|
||||||
std::lock_guard lock{mutex};
|
|
||||||
return *std::localtime(&time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void put_message(level l, std::string const & message, std::string const & thread_name)
|
sink * add_sink(std::unique_ptr<sink> s)
|
||||||
{
|
{
|
||||||
using clock = std::chrono::system_clock;
|
auto ptr = s.get();
|
||||||
|
|
||||||
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(5) << l << ']'
|
|
||||||
<< ' ' << message << '\n';
|
|
||||||
|
|
||||||
auto const str = os.str();
|
|
||||||
|
|
||||||
std::lock_guard lock{sinks_mutex};
|
std::lock_guard lock{sinks_mutex};
|
||||||
|
sinks.push_back(std::move(s));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto const & sink : sinks)
|
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 (static_cast<int>(l) >= static_cast<int>(sink.second))
|
if (sinks[i].get() == stream)
|
||||||
sink.first->write(str.data(), str.size());
|
{
|
||||||
|
auto result = std::move(sinks[i]);
|
||||||
|
sinks.erase(sinks.begin() + i);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return nullptr;
|
||||||
|
|
||||||
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)
|
void put_message(level l, std::string const & message)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue