changeset 725:0dbe1842a7d8

Irccd: rework loggers, closes #793 @3h Logger now supports category/component metadata.
author David Demelier <markand@malikania.fr>
date Tue, 17 Jul 2018 22:30:00 +0200
parents 99c52213e3bd
children dfc3bf9cec7b
files irccd/main.cpp libirccd-js/irccd/js/logger_jsapi.cpp libirccd-js/irccd/js/timer_jsapi.cpp libirccd-test/irccd/test/command_test.hpp libirccd-test/irccd/test/js_test.hpp libirccd-test/irccd/test/plugin_test.cpp libirccd/irccd/daemon/irccd.cpp libirccd/irccd/daemon/irccd.hpp libirccd/irccd/daemon/logger.cpp libirccd/irccd/daemon/logger.hpp libirccd/irccd/daemon/plugin.cpp libirccd/irccd/daemon/service/plugin_service.cpp libirccd/irccd/daemon/service/plugin_service.hpp libirccd/irccd/daemon/service/rule_service.cpp libirccd/irccd/daemon/service/rule_service.hpp libirccd/irccd/daemon/service/server_service.cpp libirccd/irccd/daemon/service/server_service.hpp libirccd/irccd/daemon/service/transport_service.cpp tests/src/libirccd-js/jsapi-logger/main.cpp tests/src/libirccd/logger/main.cpp tests/src/libirccd/rules/main.cpp
diffstat 21 files changed, 563 insertions(+), 333 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/irccd/main.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -163,7 +163,7 @@
                 instance->get_log().set_verbose(true);
         }
     } catch (const std::exception& ex) {
-        instance->get_log().warning() << "irccd: " << ex.what() << std::endl;
+        instance->get_log().warning("irccd", "") << "abort: " << ex.what() << std::endl;
         usage();
     }
 
--- a/libirccd-js/irccd/js/logger_jsapi.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd-js/irccd/js/logger_jsapi.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -16,8 +16,9 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
@@ -30,11 +31,25 @@
 
 // {{{ print
 
-duk_ret_t print(duk_context* ctx, std::ostream &out)
+duk_ret_t print(duk_context* ctx, unsigned level)
 {
+    assert(level >= 0 && level <= 2);
+
     try {
-        out << "plugin " << dukx_type_traits<js_plugin>::self(ctx)->get_name() << ": ";
-        out << duk_require_string(ctx, 0) << std::endl;
+        auto& sink = dukx_type_traits<irccd>::self(ctx).get_log();
+        auto self = dukx_type_traits<js_plugin>::self(ctx);
+
+        switch (level) {
+        case 0:
+            sink.debug(static_cast<const plugin&>(*self)) << duk_require_string(ctx, 0) << std::endl;
+            break;
+        case 1:
+            sink.info(static_cast<const plugin&>(*self)) << duk_require_string(ctx, 0) << std::endl;
+            break;
+        default:
+            sink.warning(static_cast<const plugin&>(*self)) << duk_require_string(ctx, 0) << std::endl;
+            break;
+        }
     } catch (const std::exception& ex) {
         dukx_throw(ctx, ex);
     }
@@ -59,7 +74,7 @@
  */
 duk_ret_t Logger_info(duk_context* ctx)
 {
-    return print(ctx, dukx_type_traits<irccd>::self(ctx).get_log().info());
+    return print(ctx, 1);
 }
 
 // }}}
@@ -79,7 +94,7 @@
  */
 duk_ret_t Logger_warning(duk_context* ctx)
 {
-    return print(ctx, dukx_type_traits<irccd>::self(ctx).get_log().warning());
+    return print(ctx, 2);
 }
 
 // }}}
@@ -99,7 +114,7 @@
  */
 duk_ret_t Logger_debug(duk_context* ctx)
 {
-    return print(ctx, dukx_type_traits<irccd>::self(ctx).get_log().debug());
+    return print(ctx, 0);
 }
 
 // }}}
--- a/libirccd-js/irccd/js/timer_jsapi.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd-js/irccd/js/timer_jsapi.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -20,6 +20,7 @@
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/service/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
@@ -75,12 +76,12 @@
 
 void timer::handle()
 {
-    auto plugin = plugin_.lock();
+    auto plg = plugin_.lock();
 
-    if (!plugin)
+    if (!plg)
         return;
 
-    auto& ctx = plugin->get_context();
+    auto& ctx = plg->get_context();
 
     duk_get_global_string(ctx, table);
     duk_get_prop_string(ctx, -1, key().c_str());
@@ -89,8 +90,8 @@
     if (duk_pcall(ctx, 0)) {
         auto& log = dukx_type_traits<irccd>::self(ctx).get_log();
 
-        log.warning() << "plugin: " << plugin->get_name() << " timer error:" << std::endl;
-        log.warning() << "  " << dukx_stack(ctx, -1).what() << std::endl;
+        log.warning(static_cast<const plugin&>(*plg)) << "timer error:" << std::endl;
+        log.warning(static_cast<const plugin&>(*plg)) << "  " << dukx_stack(ctx, -1).what() << std::endl;
     } else
         duk_pop(ctx);
 }
@@ -201,8 +202,6 @@
     duk_del_prop_string(ctx, -1, ptr->key().c_str());
     duk_pop(ctx);
 
-    dukx_type_traits<irccd>::self(ctx).get_log().debug("timer: destroyed");
-
     delete ptr;
 
     return 0;
--- a/libirccd-test/irccd/test/command_test.hpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd-test/irccd/test/command_test.hpp	Tue Jul 17 22:30:00 2018 +0200
@@ -108,7 +108,7 @@
 
     // Add the server and the command.
     add<Commands...>();
-    daemon_->set_log(std::make_unique<silent_logger>());
+    daemon_->set_log(std::make_unique<logger::silent_sink>());
 
     // Wait for controller to connect.
     boost::asio::deadline_timer timer(service_);
--- a/libirccd-test/irccd/test/js_test.hpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd-test/irccd/test/js_test.hpp	Tue Jul 17 22:30:00 2018 +0200
@@ -82,7 +82,7 @@
     : plugin_(new js_plugin(plugin_path))
     , server_(new journal_server(service_, "test"))
 {
-    irccd_.set_log(std::make_unique<silent_logger>());
+    irccd_.set_log(std::make_unique<logger::silent_sink>());
 
     // Irccd is mandatory at the moment.
     add<irccd_jsapi>();
--- a/libirccd-test/irccd/test/plugin_test.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd-test/irccd/test/plugin_test.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -46,7 +46,7 @@
     server_->set_nickname("irccd");
     plugin_ = std::make_unique<js_plugin>(std::move(path));
 
-    irccd_.set_log(std::make_unique<silent_logger>());
+    irccd_.set_log(std::make_unique<logger::silent_sink>());
     irccd_.get_log().set_verbose(false);
     irccd_.plugins().add("test", plugin_);
     irccd_.servers().add(server_);
--- a/libirccd/irccd/daemon/irccd.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/irccd.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -35,49 +35,70 @@
 
 namespace {
 
-class log_filter : public logger_filter {
+class format_filter : public logger::filter {
 private:
     std::string info_;
     std::string warning_;
     std::string debug_;
 
-    std::string convert(const std::string& tmpl, std::string input) const
-    {
-        if (tmpl.empty())
-            return input;
-
-        string_util::subst params;
-
-        params.flags &= ~(string_util::subst_flags::irc_attrs);
-        params.flags |= string_util::subst_flags::shell_attrs;
-        params.keywords.emplace("message", std::move(input));
-
-        return string_util::format(tmpl, params);
-    }
+    auto convert(const std::string&,
+                 std::string_view,
+                 std::string_view,
+                 std::string_view) const -> std::string;
 
 public:
-    inline log_filter(std::string info, std::string warning, std::string debug) noexcept
-        : info_(std::move(info))
-        , warning_(std::move(warning))
-        , debug_(std::move(debug))
-    {
-    }
+    format_filter(std::string info, std::string warning, std::string debug) noexcept;
+    auto pre_debug(std::string_view, std::string_view, std::string_view) const -> std::string override;
+    auto pre_info(std::string_view, std::string_view, std::string_view) const -> std::string override;
+    auto pre_warning(std::string_view, std::string_view, std::string_view) const -> std::string override;
+};
+
+auto format_filter::convert(const std::string& tmpl,
+                            std::string_view category,
+                            std::string_view component,
+                            std::string_view message) const -> std::string
+{
+    if (tmpl.empty())
+        return pre(category, component, message);
+
+    string_util::subst params;
+
+    params.flags &= ~(string_util::subst_flags::irc_attrs);
+    params.flags |= string_util::subst_flags::shell_attrs;
+    params.keywords.emplace("category", std::string(category));
+    params.keywords.emplace("component", std::string(component));
+    params.keywords.emplace("message", std::string(message));
+
+    return string_util::format(tmpl, params);
+}
 
-    std::string pre_debug(std::string input) const override
-    {
-        return convert(debug_, std::move(input));
-    }
+format_filter::format_filter(std::string info, std::string warning, std::string debug) noexcept
+    : info_(std::move(info))
+    , warning_(std::move(warning))
+    , debug_(std::move(debug))
+{
+}
+
+auto format_filter::pre_debug(std::string_view category,
+                              std::string_view component,
+                              std::string_view message) const -> std::string
+{
+    return convert(debug_, category, component, message);
+}
 
-    std::string pre_info(std::string input) const override
-    {
-        return convert(info_, std::move(input));
-    }
+auto format_filter::pre_info(std::string_view category,
+                             std::string_view component,
+                             std::string_view message) const -> std::string
+{
+    return convert(info_, category, component, message);
+}
 
-    std::string pre_warning(std::string input) const override
-    {
-        return convert(warning_, std::move(input));
-    }
-};
+auto format_filter::pre_warning(std::string_view category,
+                                std::string_view component,
+                                std::string_view message) const -> std::string
+{
+    return convert(warning_, category, component, message);
+}
 
 } // !namespace
 
@@ -102,18 +123,18 @@
         errors = it->value();
 
     try {
-        logger_ = std::make_unique<file_logger>(std::move(normal), std::move(errors));
+        sink_ = std::make_unique<logger::file_sink>(std::move(normal), std::move(errors));
     } catch (const std::exception& ex) {
-        logger_->warning() << "logs: " << ex.what() << std::endl;
+        sink_->warning("logs", "") << ex.what() << std::endl;
     }
 }
 
 void irccd::load_logs_syslog()
 {
 #if defined(HAVE_SYSLOG)
-    logger_ = std::make_unique<syslog_logger>();
+    sink_ = std::make_unique<logger::syslog_sink>();
 #else
-    logger_->warning() << "logs: syslog is not available on this platform" << std::endl;
+    sink_->warning() << "logs: syslog is not available on this platform" << std::endl;
 #endif // !HAVE_SYSLOG
 }
 
@@ -124,7 +145,7 @@
     if (sc.empty())
         return;
 
-    logger_->set_verbose(string_util::is_identifier(sc.get("verbose").value()));
+    sink_->set_verbose(string_util::is_identifier(sc.get("verbose").value()));
 
     const auto type = sc.get("type").value();
 
@@ -135,7 +156,7 @@
         else if (type == "syslog")
             load_logs_syslog();
         else if (type != "console")
-            logger_->warning() << "logs: invalid log type '" << type << std::endl;
+            sink_->warning("logs", "") << "invalid log type '" << type << std::endl;
     }
 }
 
@@ -146,7 +167,7 @@
     if (sc.empty())
         return;
 
-    logger_->set_filter(std::make_unique<log_filter>(
+    sink_->set_filter(std::make_unique<format_filter>(
         sc.get("info").value(),
         sc.get("warning").value(),
         sc.get("debug").value()
@@ -156,7 +177,7 @@
 irccd::irccd(boost::asio::io_service& service, std::string config)
     : config_(std::move(config))
     , service_(service)
-    , logger_(std::make_unique<console_logger>())
+    , sink_(std::make_unique<logger::console_sink>())
     , server_service_(std::make_unique<server_service>(*this))
     , tpt_service_(std::make_unique<transport_service>(*this))
     , rule_service_(std::make_unique<rule_service>(*this))
@@ -166,11 +187,11 @@
 
 irccd::~irccd() = default;
 
-void irccd::set_log(std::unique_ptr<logger> logger) noexcept
+void irccd::set_log(std::unique_ptr<logger::sink> sink) noexcept
 {
-    assert(logger);
+    assert(sink);
 
-    logger_ = std::move(logger);
+    sink_ = std::move(sink);
 }
 
 void irccd::load() noexcept
@@ -187,9 +208,9 @@
     load_formats();
 
     if (!loaded_)
-        logger_->info() << "irccd: loading configuration from " << config_.get_path() << std::endl;
+        sink_->info("irccd", "") << "loading configuration from " << config_.get_path() << std::endl;
     else
-        logger_->info() << "irccd: reloading configuration" << std::endl;
+        sink_->info("irccd", "") << "reloading configuration" << std::endl;
 
     if (!loaded_)
         tpt_service_->load(config_);
--- a/libirccd/irccd/daemon/irccd.hpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/irccd.hpp	Tue Jul 17 22:30:00 2018 +0200
@@ -38,7 +38,12 @@
  */
 namespace irccd {
 
-class logger;
+namespace logger {
+
+class sink;
+
+} // !logger
+
 class plugin_service;
 class rule_service;
 class server_service;
@@ -59,7 +64,7 @@
     bool loaded_{false};
 
     // Custom logger.
-    std::unique_ptr<logger> logger_;
+    std::unique_ptr<logger::sink> sink_;
 
     // Services.
     std::shared_ptr<server_service> server_service_;
@@ -139,9 +144,9 @@
      *
      * \return the logger
      */
-    inline const logger& get_log() const noexcept
+    inline const logger::sink& get_log() const noexcept
     {
-        return *logger_;
+        return *sink_;
     }
 
     /**
@@ -149,9 +154,9 @@
      *
      * \return the logger
      */
-    inline logger& get_log() noexcept
+    inline logger::sink& get_log() noexcept
     {
-        return *logger_;
+        return *sink_;
     }
 
     /**
@@ -160,7 +165,7 @@
      * \pre logger != nullptr
      * \param logger the new logger
      */
-    void set_log(std::unique_ptr<logger> logger) noexcept;
+    void set_log(std::unique_ptr<logger::sink> sink) noexcept;
 
     /**
      * Access the server service.
--- a/libirccd/irccd/daemon/logger.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/logger.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -27,38 +27,41 @@
 #  include <syslog.h>
 #endif // !HAVE_SYSLOG
 
-namespace irccd {
+namespace irccd::logger {
 
-void logger::buffer::debug(std::string line)
+void logger::debug(const std::string& line)
 {
     // Print only in debug mode, the buffer is flushed anyway.
 #if !defined(NDEBUG)
-    parent_.write_debug(parent_.filter_->pre_debug(std::move(line)));
+    parent_.write_debug(parent_.filter_->pre_debug(category_, component_, line));
 #else
     (void)line;
 #endif
 }
 
-void logger::buffer::info(std::string line)
+void logger::info(const std::string& line)
 {
     // Print only if verbose, the buffer will be flushed anyway.
     if (parent_.verbose_)
-        parent_.write_info(parent_.filter_->pre_info(std::move(line)));
+        parent_.write_info(parent_.filter_->pre_info(category_, component_, line));
 }
 
-void logger::buffer::warning(std::string line)
+void logger::warning(const std::string& line)
 {
-    parent_.write_warning(parent_.filter_->pre_warning(std::move(line)));
+    parent_.write_warning(parent_.filter_->pre_warning(category_, component_, line));
 }
 
-logger::buffer::buffer(logger& parent, level level) noexcept
-    : parent_(parent)
+logger::logger(sink& parent, level level, std::string_view category, std::string_view component) noexcept
+    : std::ostream(this)
     , level_(level)
+    , parent_(parent)
+    , category_(category)
+    , component_(component)
 {
     assert(level >= level::debug && level <= level::warning);
 }
 
-int logger::buffer::sync()
+int logger::sync()
 {
     std::string buffer = str();
     std::string::size_type pos;
@@ -71,13 +74,13 @@
 
         switch (level_) {
         case level::debug:
-            debug(std::move(line));
+            debug(line);
             break;
         case level::info:
-            info(std::move(line));
+            info(line);
             break;
         case level::warning:
-            warning(std::move(line));
+            warning(line);
             break;
         default:
             break;
@@ -89,158 +92,156 @@
     return 0;
 }
 
-/*
- * console_logger
- * ------------------------------------------------------------------
- */
-
-void console_logger::write_info(const std::string& line)
+void console_sink::write_info(const std::string& line)
 {
     std::cout << line << std::endl;
 }
 
-void console_logger::write_warning(const std::string& line)
+void console_sink::write_warning(const std::string& line)
 {
     std::cerr << line << std::endl;
 }
 
-void console_logger::write_debug(const std::string& line)
+void console_sink::write_debug(const std::string& line)
 {
     std::cout << line << std::endl;
 }
 
-/*
- * file_logger
- * ------------------------------------------------------------------
- */
-
-file_logger::file_logger(std::string normal, std::string errors)
+file_sink::file_sink(std::string normal, std::string errors)
     : output_normal_(std::move(normal))
     , output_error_(std::move(errors))
 {
 }
 
-void file_logger::write_info(const std::string& line)
+void file_sink::write_info(const std::string& line)
 {
     std::ofstream(output_normal_, std::ofstream::out | std::ofstream::app) << line << std::endl;
 }
 
-void file_logger::write_warning(const std::string& line)
+void file_sink::write_warning(const std::string& line)
 {
     std::ofstream(output_error_, std::ofstream::out | std::ofstream::app) << line << std::endl;
 }
 
-void file_logger::write_debug(const std::string& line)
+void file_sink::write_debug(const std::string& line)
 {
     std::ofstream(output_normal_, std::ofstream::out | std::ofstream::app) << line << std::endl;
 }
 
-/*
- * silent_logger
- * ------------------------------------------------------------------
- */
-
-void silent_logger::write_info(const std::string&)
+void silent_sink::write_info(const std::string&)
 {
 }
 
-void silent_logger::write_warning(const std::string&)
+void silent_sink::write_warning(const std::string&)
 {
 }
 
-void silent_logger::write_debug(const std::string&)
+void silent_sink::write_debug(const std::string&)
 {
 }
 
-/*
- * syslog_logger
- * ------------------------------------------------------------------
- */
-
 #if defined(HAVE_SYSLOG)
 
-syslog_logger::syslog_logger()
+syslog_sink::syslog_sink()
 {
     openlog("irccd", LOG_PID, LOG_DAEMON);
 }
 
-syslog_logger::~syslog_logger()
+syslog_sink::~syslog_sink()
 {
     closelog();
 }
 
-void syslog_logger::write_info(const std::string& line)
+void syslog_sink::write_info(const std::string& line)
 {
     syslog(LOG_INFO | LOG_USER, "%s", line.c_str());
 }
 
-void syslog_logger::write_warning(const std::string& line)
+void syslog_sink::write_warning(const std::string& line)
 {
     syslog(LOG_WARNING | LOG_USER, "%s", line.c_str());
 }
 
-void syslog_logger::write_debug(const std::string& line)
+void syslog_sink::write_debug(const std::string& line)
 {
     syslog(LOG_DEBUG | LOG_USER, "%s", line.c_str());
 }
 
 #endif // !HAVE_SYSLOG
 
-/*
- * logger
- * ------------------------------------------------------------------
- */
-
-logger::logger()
-    : buffer_info_(*this, buffer::level::info)
-    , buffer_warning_(*this, buffer::level::warning)
-    , buffer_debug_(*this, buffer::level::debug)
-    , stream_info_(&buffer_info_)
-    , stream_warning_(&buffer_warning_)
-    , stream_debug_(&buffer_debug_)
-    , filter_(std::make_unique<logger_filter>())
+sink::sink()
+    : filter_(new filter)
 {
 }
 
-bool logger::is_verbose() const noexcept
+auto sink::is_verbose() const noexcept -> bool
 {
     return verbose_;
 }
 
-void logger::set_verbose(bool mode) noexcept
+void sink::set_verbose(bool mode) noexcept
 {
     verbose_ = mode;
 }
 
-void logger::set_filter(std::unique_ptr<logger_filter> newfilter) noexcept
+void sink::set_filter(std::unique_ptr<filter> filter) noexcept
 {
-    assert(newfilter);
+    assert(filter);
 
-    filter_ = std::move(newfilter);
+    filter_ = std::move(filter);
 }
 
-std::ostream& logger::info(const std::string& message)
+auto sink::info(std::string_view category, std::string_view component) -> logger
+{
+    return logger(*this, logger::level::info, category, component);;
+}
+
+auto sink::warning(std::string_view category, std::string_view component) -> logger
 {
-    if (!message.empty())
-        stream_info_ << message << std::endl;
+    return logger(*this, logger::level::warning, category, component);;
+}
 
-    return stream_info_;
+auto sink::debug(std::string_view category, std::string_view component) -> logger
+{
+    return logger(*this, logger::level::debug, category, component);;
 }
 
-std::ostream& logger::warning(const std::string& message)
+auto filter::pre(std::string_view category,
+                 std::string_view component,
+                 std::string_view message) const -> std::string
 {
-    if (!message.empty())
-        stream_warning_ << message << std::endl;
+    std::ostringstream oss;
+
+    oss << category;
 
-    return stream_warning_;
+    if (!component.empty())
+        oss << " " << component;
+
+    oss << ": ";
+    oss << message;
+
+    return oss.str();
 }
 
-std::ostream& logger::debug(const std::string& message)
+auto filter::pre_debug(std::string_view category,
+                       std::string_view component,
+                       std::string_view message) const -> std::string
 {
-    if (!message.empty())
-        stream_debug_ << message << std::endl;
-
-    return stream_debug_;
+    return pre(category, component, message);
 }
 
-} // !irccd
+auto filter::pre_info(std::string_view category,
+                      std::string_view component,
+                      std::string_view message) const -> std::string
+{
+    return pre(category, component, message);
+}
+
+auto filter::pre_warning(std::string_view category,
+                        std::string_view component,
+                        std::string_view message) const -> std::string
+{
+    return pre(category, component, message);
+}
+
+} // !irccd::logger
--- a/libirccd/irccd/daemon/logger.hpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/logger.hpp	Tue Jul 17 22:30:00 2018 +0200
@@ -29,11 +29,49 @@
 #include <memory>
 #include <sstream>
 #include <string>
+#include <string_view>
 #include <utility>
 
-namespace irccd {
+namespace irccd::logger {
+
+class filter;
+class sink;
+
+/**
+ * \brief Traits for loggable objects.
+ *
+ * Specialize this structure and add the following static functions to be able
+ * to log object with convenience:
+ *
+ * ```cpp
+ * static auto get_category(const T&) noexcept -> std::string_view;
+ * static auto get_component(const T&) noexcept -> std::string_view;
+ * ```
+ */
+template <typename T>
+struct loggable_traits;
 
-class logger_filter;
+class logger : public std::ostream, public std::stringbuf {
+private:
+    friend class sink;
+
+    enum class level {
+        debug,
+        info,
+        warning
+    } level_;
+
+    sink& parent_;
+
+    std::string_view category_;
+    std::string_view component_;
+
+    void debug(const std::string&);
+    void info(const std::string&);
+    void warning(const std::string&);
+    auto sync() -> int override;
+    logger(sink&, level, std::string_view, std::string_view) noexcept;
+};
 
 /**
  * \brief Interface to implement new logger mechanisms.
@@ -41,48 +79,18 @@
  * Derive from this class and implement write_info, write_warning and
  * write_debug functions.
  *
- * \see file_logger
- * \see console_logger
- * \see syslog_logger
- * \see silent_logger
+ * \see file_sink
+ * \see console_sink
+ * \see syslog_sink
+ * \see silent_sink
  */
-class logger {
+class sink {
 private:
-    class buffer : public std::stringbuf {
-    public:
-        enum class level {
-            debug,
-            info,
-            warning
-        };
-
-    private:
-        logger& parent_;
-        level level_;
-
-        void debug(std::string line);
-        void info(std::string line);
-        void warning(std::string line);
-
-    public:
-        buffer(logger& parent, level level) noexcept;
-
-        virtual int sync() override;
-    };
-
-    // Buffers.
-    buffer buffer_info_;
-    buffer buffer_warning_;
-    buffer buffer_debug_;
-
-    // Stream outputs.
-    std::ostream stream_info_;
-    std::ostream stream_warning_;
-    std::ostream stream_debug_;
+    friend class logger;
 
     // User options.
     bool verbose_{false};
-    std::unique_ptr<logger_filter> filter_;
+    std::unique_ptr<filter> filter_;
 
 protected:
     /**
@@ -119,19 +127,19 @@
     /**
      * Default constructor.
      */
-    logger();
+    sink();
 
     /**
      * Virtual destructor defaulted.
      */
-    virtual ~logger() = default;
+    virtual ~sink() = default;
 
     /**
      * Tells if logger is verbose.
      *
      * \return true if verbose
      */
-    bool is_verbose() const noexcept;
+    auto is_verbose() const noexcept -> bool;
 
     /**
      * Set the verbosity mode.
@@ -146,92 +154,158 @@
      * \pre filter must not be null
      * \param filter the filter
      */
-    void set_filter(std::unique_ptr<logger_filter> filter) noexcept;
+    void set_filter(std::unique_ptr<filter> filter) noexcept;
 
     /**
      * Get the stream for informational messages.
      *
      * If message is specified, a new line character is appended.
      *
-     * \param message the optional message to write
-     * \return the stream
+     * \param category the category subsystem
+     * \param component the optional component
+     * \return the output stream
      * \note Has no effect if verbose is set to false.
      */
-    std::ostream& info(const std::string& message = "");
+    auto info(std::string_view category, std::string_view component) -> logger;
+
+    /**
+     * Convenient function with loggable objects.
+     *
+     * \param loggable the loggable object
+     * \return the output stream
+     * \see loggable_traits
+     */
+    template <typename Loggable>
+    auto info(const Loggable& loggable) -> logger
+    {
+        return info(
+            loggable_traits<Loggable>::get_category(loggable),
+            loggable_traits<Loggable>::get_component(loggable)
+        );
+    }
 
     /**
      * Get the stream for warnings.
      *
      * If message is specified, a new line character is appended.
      *
-     * \param message the optional message to write
-     * \return the stream
+     * \param category the category subsystem
+     * \param component the optional component
+     * \return the output stream
      */
-    std::ostream& warning(const std::string& message = "");
+    auto warning(std::string_view category, std::string_view component) -> logger;
+
+    /**
+     * Convenient function with loggable objects.
+     *
+     * \param loggable the loggable object
+     * \return the output stream
+     * \see loggable_traits
+     */
+    template <typename Loggable>
+    auto warning(const Loggable& loggable) -> logger
+    {
+        return warning(
+            loggable_traits<Loggable>::get_category(loggable),
+            loggable_traits<Loggable>::get_component(loggable)
+        );
+    }
 
     /**
      * Get the stream for debug messages.
      *
      * If message is specified, a new line character is appended.
      *
-     * \param message the optional message to write
-     * \return the stream
+     * \param category the category subsystem
+     * \param component the optional component
+     * \return the output stream
      * \note Has no effect if compiled in release mode.
      */
-    std::ostream& debug(const std::string& message = "");
+    auto debug(std::string_view category, std::string_view component) -> logger;
+
+    /**
+     * Convenient function with loggable objects.
+     *
+     * \param loggable the loggable object
+     * \return the output stream
+     * \see loggable_traits
+     */
+    template <typename Loggable>
+    auto debug(const Loggable& loggable) -> logger
+    {
+        return debug(
+            loggable_traits<Loggable>::get_category(loggable),
+            loggable_traits<Loggable>::get_component(loggable)
+        );
+    }
 };
 
 /**
  * \brief Filter messages before printing them.
- *
- * Derive from this class and use log::setFilter.
  */
-class logger_filter {
+class filter {
+private:
 public:
     /**
      * Virtual destructor defaulted.
      */
-    virtual ~logger_filter() = default;
+    virtual ~filter() = default;
+
+    /**
+     * Default function called for each virtual ones.
+     *
+     * \param category the category subsystem
+     * \param component the optional component
+     * \param message the message
+     * \return default formatted message
+     */
+    auto pre(std::string_view category,
+             std::string_view component,
+             std::string_view message) const -> std::string;
+
 
     /**
      * Update the debug message.
      *
-     * \param input the message
-     * \return the updated message
+     * \param category the category subsystem
+     * \param component the optional component
+     * \param message the message
+     * \return the message
      */
-    virtual std::string pre_debug(std::string input) const
-    {
-        return input;
-    }
+    virtual auto pre_debug(std::string_view category,
+                           std::string_view component,
+                           std::string_view message) const -> std::string;
 
     /**
      * Update the information message.
      *
-     * \param input the message
+     * \param category the category subsystem
+     * \param component the optional component
+     * \param message the message
      * \return the updated message
      */
-    virtual std::string pre_info(std::string input) const
-    {
-        return input;
-    }
+    virtual auto pre_info(std::string_view category,
+                          std::string_view component,
+                          std::string_view message) const -> std::string;
 
     /**
      * Update the warning message.
      *
-     * \param input the message
+     * \param category the category subsystem
+     * \param component the optional component
+     * \param message the message
      * \return the updated message
      */
-    virtual std::string pre_warning(std::string input) const
-    {
-        return input;
-    }
+    virtual auto pre_warning(std::string_view category,
+                             std::string_view component,
+                             std::string_view message) const -> std::string;
 };
 
 /**
  * \brief Logger implementation for console output using std::cout and
  *        std::cerr.
  */
-class console_logger : public logger {
+class console_sink : public sink {
 protected:
     /**
      * \copydoc logger::write_debug
@@ -252,7 +326,7 @@
 /**
  * \brief Output to a files.
  */
-class file_logger : public logger {
+class file_sink : public sink {
 private:
     std::string output_normal_;
     std::string output_error_;
@@ -280,7 +354,7 @@
      * \param normal the path to the normal logs
      * \param errors the path to the errors logs
      */
-    file_logger(std::string normal, std::string errors);
+    file_sink(std::string normal, std::string errors);
 };
 
 /**
@@ -288,7 +362,7 @@
  *
  * Useful for unit tests when some classes may emits log.
  */
-class silent_logger : public logger {
+class silent_sink : public sink {
 protected:
     /**
      * \copydoc logger::write_debug
@@ -311,7 +385,7 @@
 /**
  * \brief Implements logger into syslog.
  */
-class syslog_logger : public logger {
+class syslog_sink : public sink {
 protected:
     /**
      * \copydoc logger::write_debug
@@ -332,16 +406,16 @@
     /**
      * Open the syslog.
      */
-    syslog_logger();
+    syslog_sink();
 
     /**
      * Close the syslog.
      */
-    ~syslog_logger();
+    ~syslog_sink();
 };
 
 #endif // !HAVE_SYSLOG
 
-} // !irccd
+} // !irccd::logger
 
 #endif // !IRCCD_DAEMON_LOGGER_HPP
--- a/libirccd/irccd/daemon/plugin.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/plugin.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -68,13 +68,13 @@
 {
     std::ostringstream oss;
 
-    oss << "plugin " << name_ << ": " << code().message();
+    oss << code().message();
 
     std::istringstream iss(message_);
     std::string line;
 
     while (getline(iss, line))
-        oss << "\nplugin " << name_ << ": " << line;
+        oss << "\n" << line;
 
     what_ = oss.str();
 }
--- a/libirccd/irccd/daemon/service/plugin_service.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/service/plugin_service.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -52,7 +52,7 @@
         try {
             plugin->handle_unload(irccd_);
         } catch (const std::exception& ex) {
-            irccd_.get_log().warning() << "plugin: " << plugin->get_name() << ": " << ex.what() << std::endl;
+            irccd_.get_log().warning(*plugin) << ex.what() << std::endl;
         }
     }
 }
@@ -153,7 +153,7 @@
             if (plugin)
                 return plugin;
         } catch (const std::exception& ex) {
-            irccd_.get_log().warning() << "plugin " << id << ": " << ex.what() << std::endl;
+            irccd_.get_log().warning("plugin", id) << ex.what() << std::endl;
         }
     }
 
@@ -225,10 +225,25 @@
             try {
                 load(name, option.value());
             } catch (const std::exception& ex) {
-                irccd_.get_log().warning(ex.what());
+                irccd_.get_log().warning("plugin", name) << ex.what() << std::endl;
             }
         }
     }
 }
 
+namespace logger {
+
+auto loggable_traits<plugin>::get_category(const plugin&) -> std::string_view
+{
+    return "plugin";
+}
+
+auto loggable_traits<plugin>::get_component(const plugin& plugin) -> std::string_view
+{
+    // TODO: get id instead.
+    return plugin.get_name();
+}
+
+} // !logger
+
 } // !irccd
--- a/libirccd/irccd/daemon/service/plugin_service.hpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/service/plugin_service.hpp	Tue Jul 17 22:30:00 2018 +0200
@@ -27,6 +27,7 @@
 #include <cassert>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <unordered_map>
 #include <vector>
 
@@ -244,6 +245,35 @@
     void load(const config& cfg) noexcept;
 };
 
+namespace logger {
+
+template <typename T>
+struct loggable_traits;
+
+/**
+ * \brief Implement Loggable traits for plugin.
+ */
+template <>
+struct loggable_traits<plugin> {
+    /**
+     * Return "plugin"
+     *
+     * \param plugin the plugin
+     * \return the category
+     */
+    static auto get_category(const plugin& plugin) -> std::string_view;
+
+    /**
+     * Return the plugin id.
+     *
+     * \param plugin the plugin
+     * \return the plugin id
+     */
+    static auto get_component(const plugin& plugin) -> std::string_view;
+};
+
+} // !logger
+
 } // !irccd
 
 #endif // !IRCCD_DAEMON_PLUGIN_SERVICE_HPP
--- a/libirccd/irccd/daemon/service/rule_service.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/service/rule_service.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -77,22 +77,24 @@
 {
     bool result = true;
 
-    irccd_.get_log().debug(string_util::sprintf(
-        "rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s",
-        server, channel, origin, plugin, event
-    ));
+    irccd_.get_log().debug("rule", "")
+        << "solving for server=" << server
+        << ", channel=" << channel
+        << ", origin=" << origin
+        << ", plugin=" << plugin
+        << ", event=" << event << std::endl;
 
     int i = 0;
     for (const auto& rule : rules_) {
         auto action = rule.get_action() == rule::action::accept ? "accept" : "drop";
 
-        irccd_.get_log().debug() << "  candidate "   << i++ << ":\n"
-            << "    servers: "  << string_util::join(rule.get_servers()) << "\n"
-            << "    channels: " << string_util::join(rule.get_channels()) << "\n"
-            << "    origins: "  << string_util::join(rule.get_origins()) << "\n"
-            << "    plugins: "  << string_util::join(rule.get_plugins()) << "\n"
-            << "    events: "   << string_util::join(rule.get_events()) << "\n"
-            << "    action: "   << action << std::endl;
+        irccd_.get_log().debug(rule) << "candidate "  << i++ << ":" << std::endl;
+        irccd_.get_log().debug(rule) << "  servers: "  << string_util::join(rule.get_servers()) << std::endl;
+        irccd_.get_log().debug(rule) << "  channels: " << string_util::join(rule.get_channels()) << std::endl;
+        irccd_.get_log().debug(rule) << "  origins: "  << string_util::join(rule.get_origins()) << std::endl;
+        irccd_.get_log().debug(rule) << "  plugins: "  << string_util::join(rule.get_plugins()) << std::endl;
+        irccd_.get_log().debug(rule) << "  events: "   << string_util::join(rule.get_events()) << std::endl;
+        irccd_.get_log().debug(rule) << "  action: "   << action << std::endl;
 
         if (rule.match(server, channel, origin, plugin, event))
             result = rule.get_action() == rule::action::accept;
@@ -112,9 +114,23 @@
         try {
             rules_.push_back(rule_util::from_config(section));
         } catch (const std::exception& ex) {
-            irccd_.get_log().warning() << "rule: " << ex.what() << std::endl;
+            irccd_.get_log().warning("rule", "") << ex.what() << std::endl;
         }
     }
 }
 
+namespace logger {
+
+auto loggable_traits<rule>::get_category(const rule&) -> std::string_view
+{
+    return "rule";
+}
+
+auto loggable_traits<rule>::get_component(const rule&) -> std::string_view
+{
+    return "";
+}
+
+} // !logger
+
 } // !irccd
--- a/libirccd/irccd/daemon/service/rule_service.hpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/service/rule_service.hpp	Tue Jul 17 22:30:00 2018 +0200
@@ -133,6 +133,20 @@
     void load(const config& cfg) noexcept;
 };
 
+namespace logger {
+
+template <typename T>
+struct loggable_traits;
+
+template <>
+struct loggable_traits<rule> {
+    static auto get_category(const rule& rule) -> std::string_view;
+
+    static auto get_component(const rule& rule) -> std::string_view;
+};
+
+} // !logger
+
 } // !irccd
 
 #endif // !IRCCD_DAEMON_RULE_SERVICE_HPP
--- a/libirccd/irccd/daemon/service/server_service.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/service/server_service.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -45,17 +45,16 @@
         const auto allowed = daemon.rules().solve(server, target, origin, plugin->get_name(), eventname);
 
         if (!allowed) {
-            daemon.get_log().debug("rule: event skipped on match");
+            daemon.get_log().debug("rule", "") << "event skipped on match" << std::endl;
             continue;
         }
 
-        daemon.get_log().debug("rule: event allowed");
+        daemon.get_log().debug("rule", "") << "event allowed" << std::endl;
 
         try {
             exec_func(*plugin);
         } catch (const std::exception& ex) {
-            daemon.get_log().warning() << "plugin " << plugin->get_name() << ": error: "
-                << ex.what() << std::endl;
+            daemon.get_log().warning(*plugin) << ex.what() << std::endl;
         }
     }
 }
@@ -64,7 +63,7 @@
 
 void server_service::handle_connect(const connect_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onConnect" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onConnect" << std::endl;
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onConnect"             },
         { "server",     ev.server->get_name()   }
@@ -82,7 +81,7 @@
 
 void server_service::handle_disconnect(const disconnect_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onDisconnect" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onDisconnect" << std::endl;
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onDisconnect"          },
         { "server",     ev.server->get_name()   }
@@ -105,10 +104,10 @@
 
 void server_service::handle_invite(const invite_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onInvite:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  target: " << ev.nickname << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onInvite:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  target: " << ev.nickname << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onInvite"              },
@@ -129,9 +128,9 @@
 
 void server_service::handle_join(const join_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onJoin:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onJoin:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onJoin"                },
@@ -152,11 +151,11 @@
 
 void server_service::handle_kick(const kick_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onKick:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  target: " << ev.target << "\n";
-    irccd_.get_log().debug() << "  reason: " << ev.reason << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onKick:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  target: " << ev.target << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onKick"                },
@@ -179,10 +178,10 @@
 
 void server_service::handle_message(const message_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onMessage:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  message: " << ev.message << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onMessage:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onMessage"             },
@@ -220,10 +219,10 @@
 
 void server_service::handle_me(const me_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onMe:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  target: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  message: " << ev.message << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onMe:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  target: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onMe"                  },
@@ -245,13 +244,13 @@
 
 void server_service::handle_mode(const mode_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onMode\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  mode: " << ev.mode << "\n";
-    irccd_.get_log().debug() << "  limit: " << ev.limit << "\n";
-    irccd_.get_log().debug() << "  user: " << ev.user << "\n";
-    irccd_.get_log().debug() << "  mask: " << ev.mask << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onMode" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  mode: " << ev.mode << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  limit: " << ev.limit << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  user: " << ev.user << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  mask: " << ev.mask << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onMode"                },
@@ -276,9 +275,9 @@
 
 void server_service::handle_names(const names_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onNames:\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onNames:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
 
     auto names = nlohmann::json::array();
 
@@ -304,9 +303,9 @@
 
 void server_service::handle_nick(const nick_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onNick:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  nickname: " << ev.nickname << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onNick:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.nickname << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onNick"                },
@@ -327,10 +326,10 @@
 
 void server_service::handle_notice(const notice_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onNotice:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  message: " << ev.message << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onNotice:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  message: " << ev.message << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onNotice"              },
@@ -352,10 +351,10 @@
 
 void server_service::handle_part(const part_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onPart:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  reason: " << ev.reason << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onPart:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  reason: " << ev.reason << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onPart"                },
@@ -377,10 +376,10 @@
 
 void server_service::handle_topic(const topic_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onTopic:\n";
-    irccd_.get_log().debug() << "  origin: " << ev.origin << "\n";
-    irccd_.get_log().debug() << "  channel: " << ev.channel << "\n";
-    irccd_.get_log().debug() << "  topic: " << ev.topic << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onTopic:" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  origin: " << ev.origin << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channel: " << ev.channel << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  topic: " << ev.topic << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onTopic"               },
@@ -402,12 +401,12 @@
 
 void server_service::handle_whois(const whois_event& ev)
 {
-    irccd_.get_log().debug() << "server " << ev.server->get_name() << ": event onWhois\n";
-    irccd_.get_log().debug() << "  nickname: " << ev.whois.nick << "\n";
-    irccd_.get_log().debug() << "  username: " << ev.whois.user << "\n";
-    irccd_.get_log().debug() << "  host: " << ev.whois.host << "\n";
-    irccd_.get_log().debug() << "  realname: " << ev.whois.realname << "\n";
-    irccd_.get_log().debug() << "  channels: " << string_util::join(ev.whois.channels, ", ") << std::endl;
+    irccd_.get_log().debug(*ev.server) << "event onWhois" << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  nickname: " << ev.whois.nick << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  username: " << ev.whois.user << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  host: " << ev.whois.host << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  realname: " << ev.whois.realname << std::endl;
+    irccd_.get_log().debug(*ev.server) << "  channels: " << string_util::join(ev.whois.channels, ", ") << std::endl;
 
     irccd_.transports().broadcast(nlohmann::json::object({
         { "event",      "onWhois"               },
@@ -520,6 +519,8 @@
         if (section.key() != "server")
             continue;
 
+        const auto id = section.get("name").value();
+
         try {
             auto server = server_util::from_config(irccd_.get_service(), cfg, section);
 
@@ -528,10 +529,24 @@
 
             add(std::move(server));
         } catch (const std::exception& ex) {
-            irccd_.get_log().warning() << "server " << section.get("name").value() << ": "
-                << ex.what() << std::endl;
+            irccd_.get_log().warning("server", id) << ex.what() << std::endl;
         }
     }
 }
 
+namespace logger {
+
+auto loggable_traits<server>::get_category(const server&) -> std::string_view
+{
+    return "server";
+}
+
+auto loggable_traits<server>::get_component(const server& sv) -> std::string_view
+{
+    // TODO: get id instead.
+    return sv.get_name();
+}
+
+} // !logger
+
 } // !irccd
--- a/libirccd/irccd/daemon/service/server_service.hpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/service/server_service.hpp	Tue Jul 17 22:30:00 2018 +0200
@@ -133,6 +133,20 @@
     void load(const config& cfg) noexcept;
 };
 
+namespace logger {
+
+template <typename T>
+struct loggable_traits;
+
+template <>
+struct loggable_traits<server> {
+    static auto get_category(const server& server) -> std::string_view;
+
+    static auto get_component(const server& server) -> std::string_view;
+};
+
+} // !logger
+
 } // !irccd
 
 #endif // !IRCCD_DAEMON_SERVER_SERVICE_HPP
--- a/libirccd/irccd/daemon/service/transport_service.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/libirccd/irccd/daemon/service/transport_service.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -56,8 +56,9 @@
         } catch (const std::system_error& ex) {
             tc->error(ex.code(), (*cmd)->get_name());
         } catch (const std::exception& ex) {
-            irccd_.get_log().warning() << "transport: unknown error not reported" << std::endl;
-            irccd_.get_log().warning() << "transport: " << ex.what() << std::endl;
+            irccd_.get_log().warning("transport", "")
+                << "unknown error not reported: "
+                << ex.what() << std::endl;
         }
     }
 }
@@ -67,7 +68,7 @@
     tc->read([this, tc] (auto code, auto json) {
         switch (static_cast<std::errc>(code.value())) {
         case std::errc::not_connected:
-            irccd_.get_log().info("transport: client disconnected");
+            irccd_.get_log().info("transport", "") << "client disconnected" << std::endl;
             break;
         case std::errc::invalid_argument:
             tc->error(irccd_error::invalid_message);
@@ -93,7 +94,7 @@
             do_accept(ts);
             do_recv(std::move(client));
 
-            irccd_.get_log().info() << "transport: new client connected" << std::endl;
+            irccd_.get_log().info("transport", "") << "new client connected" << std::endl;
         }
     });
 }
@@ -131,7 +132,7 @@
         try {
             add(transport_util::from_config(irccd_.get_service(), section));
         } catch (const std::exception& ex) {
-            irccd_.get_log().warning() << "transport: " << ex.what() << std::endl;
+            irccd_.get_log().warning("transport", "") << ex.what() << std::endl;
         }
     }
 }
--- a/tests/src/libirccd-js/jsapi-logger/main.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-logger/main.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -36,12 +36,12 @@
     std::string line_warning;
     std::string line_debug;
 
-    class my_logger : public logger {
+    class sample_sink : public logger::sink {
     private:
         logger_test& test_;
 
     public:
-        inline my_logger(logger_test& test) noexcept
+        inline sample_sink(logger_test& test) noexcept
             : test_(test)
         {
         }
@@ -65,7 +65,7 @@
     logger_test()
         : js_test(CMAKE_CURRENT_SOURCE_DIR "/empty.js")
     {
-        irccd_.set_log(std::make_unique<my_logger>(*this));
+        irccd_.set_log(std::make_unique<sample_sink>(*this));
         irccd_.get_log().set_verbose(true);
     }
 };
--- a/tests/src/libirccd/logger/main.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/tests/src/libirccd/logger/main.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -20,14 +20,18 @@
 
 #define BOOST_TEST_MODULE "Logger"
 #include <boost/test/unit_test.hpp>
+#include <boost/format.hpp>
 
 #include <irccd/daemon/logger.hpp>
 
+using boost::format;
+using boost::str;
+
 namespace irccd {
 
 namespace {
 
-class my_logger : public logger {
+class sample_sink : public logger::sink {
 public:
     std::string line_debug;
     std::string line_info;
@@ -49,31 +53,37 @@
     }
 };
 
-class my_filter : public logger_filter {
+class sample_filter : public logger::filter {
 public:
-    std::string pre_debug(std::string input) const override
+    auto pre_debug(std::string_view category,
+                   std::string_view component,
+                   std::string_view message) const -> std::string override
     {
-        return std::reverse(input.begin(), input.end()), input;
+        return str(format("DEBUG %s:%s:%s") % category % component % message);
     }
 
-    std::string pre_info(std::string input) const override
+    auto pre_info(std::string_view category,
+                  std::string_view component,
+                  std::string_view message) const -> std::string override
     {
-        return std::reverse(input.begin(), input.end()), input;
+        return str(format("INFO %s:%s:%s") % category % component % message);
     }
 
-    std::string pre_warning(std::string input) const override
+    auto pre_warning(std::string_view category,
+                     std::string_view component,
+                     std::string_view message) const -> std::string override
     {
-        return std::reverse(input.begin(), input.end()), input;
+        return str(format("WARN %s:%s:%s") % category % component % message);
     }
 };
 
 class logger_test {
 public:
-    my_logger log_;
+    sample_sink log_;
 
     logger_test()
     {
-        log_.set_filter(std::make_unique<my_filter>());
+        log_.set_filter(std::make_unique<sample_filter>());
         log_.set_verbose(true);
     }
 };
@@ -84,33 +94,33 @@
 
 BOOST_AUTO_TEST_CASE(debug)
 {
-    log_.debug("debug");
+    log_.debug("test", "debug") << "success" << std::endl;
 
-    BOOST_REQUIRE_EQUAL("gubed", log_.line_debug);
+    BOOST_TEST(log_.line_debug == "DEBUG test:debug:success");
 }
 
 #endif
 
 BOOST_AUTO_TEST_CASE(info)
 {
-    log_.info("info");
+    log_.info("test", "info") << "success" << std::endl;
 
-    BOOST_REQUIRE_EQUAL("ofni", log_.line_info);
+    BOOST_TEST(log_.line_info == "INFO test:info:success");
 }
 
 BOOST_AUTO_TEST_CASE(info_quiet)
 {
     log_.set_verbose(false);
-    log_.info("info");
+    log_.info("test", "info") << "success" << std::endl;
 
     BOOST_REQUIRE(log_.line_info.empty());
 }
 
 BOOST_AUTO_TEST_CASE(warning)
 {
-    log_.warning("warning");
+    log_.warning("test", "warning") << "success" << std::endl;
 
-    BOOST_REQUIRE_EQUAL("gninraw", log_.line_warning);
+    BOOST_TEST(log_.line_warning == "WARN test:warning:success");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/rules/main.cpp	Tue Jul 17 20:31:00 2018 +0200
+++ b/tests/src/libirccd/rules/main.cpp	Tue Jul 17 22:30:00 2018 +0200
@@ -75,7 +75,7 @@
 
     rules_test()
     {
-        daemon_.set_log(std::make_unique<silent_logger>());
+        daemon_.set_log(std::make_unique<logger::silent_sink>());
 
         // #1
         {