diff --git a/example/example.cpp b/example/example.cpp index ec31baaa..1e78e2bd 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -26,6 +26,7 @@ void udp_example(); void custom_flags_example(); void file_events_example(); void replace_default_logger_example(); +void hierarchical_logger_example(); #include "spdlog/spdlog.h" #include "spdlog/cfg/env.h" // support for loading levels from the environment variable @@ -86,6 +87,7 @@ int main(int, char *[]) custom_flags_example(); file_events_example(); replace_default_logger_example(); + hierarchical_logger_example(); // Flush all *registered* loggers using a worker thread every 3 seconds. // note: registered loggers *must* be thread safe for this to work correctly! @@ -389,3 +391,40 @@ void replace_default_logger_example() spdlog::set_default_logger(old_logger); } + + +void hierarchical_logger_example() +{ + + spdlog::default_logger()->set_formatter(spdlog::details::make_unique("root [%n] [%^%l%$] %v")); + + auto lvl1 = spdlog::stdout_color_mt("propagate_lvl1"); + + lvl1->set_formatter(spdlog::details::make_unique("lvl1 [%n] [%^%l%$] %v")); + lvl1->set_level(spdlog::level::debug); + auto lvl2 = std::make_shared("propagate_lvl1.lvl2"); + spdlog::register_logger(lvl2); + // skip level 3 + auto lvl4 = std::make_shared("propagate_lvl1.lvl2.lvl3.lvl4"); + spdlog::register_logger(lvl4); + + + lvl4->debug("I am a debug message at Level 4 but will be printed by Level 1 logger"); + lvl2->debug("I am a debug message at Level 2 but will be printed by Level 1 logger"); + + + auto multi_lvl1 = spdlog::stdout_color_mt("multi_lvl1"); + multi_lvl1->set_level(spdlog::level::debug); + multi_lvl1->set_formatter(spdlog::details::make_unique("lvl1 [%n] [%^%l%$] %v")); + auto multi_lvl2 = spdlog::stdout_color_mt("multi_lvl1.lvl2"); + multi_lvl2->set_level(spdlog::level::info); + multi_lvl2->set_formatter(spdlog::details::make_unique("lvl2 [%n] [%^%l%$] %v")); + // skip level 3 + auto multi_lvl4 = std::make_shared("multi_lvl1.lvl2.lvl3.lvl4"); + spdlog::register_logger(multi_lvl4); + + + + multi_lvl4->debug("I am a debug message at Level 4 but will be printed by Level 1 logger"); + multi_lvl4->info("I am an info message at Level 4 but will be printed by Level 2, Level 1 and root logger"); +} diff --git a/include/spdlog/details/registry-inl.h b/include/spdlog/details/registry-inl.h index cb1fe84f..a3a10eff 100644 --- a/include/spdlog/details/registry-inl.h +++ b/include/spdlog/details/registry-inl.h @@ -219,6 +219,18 @@ SPDLOG_INLINE void registry::flush_all() SPDLOG_INLINE void registry::drop(const std::string &logger_name) { std::lock_guard lock(logger_map_mutex_); + + // Iterate over all loggers and make sure to update the root logger reference + auto drop_logger = loggers_.find(logger_name); + if (drop_logger != loggers_.end()) { + for (auto& logger : loggers_) { + if (logger.second->parent_ == drop_logger->second) { + // take the next parent + logger.second->parent_ = drop_logger->second->parent_; + } + } + } + auto is_default_logger = default_logger_ && default_logger_->name() == logger_name; loggers_.erase(logger_name); if (is_default_logger) @@ -309,6 +321,26 @@ SPDLOG_INLINE void registry::register_logger_(std::shared_ptr new_logger auto logger_name = new_logger->name(); throw_if_exists_(logger_name); loggers_[logger_name] = std::move(new_logger); + + if (logger_name.empty()) + { + // this is already the root logger, nothing to be done here + return; + } + + // get parent name or use root logger if no '.' in name + const auto& dot_pos = logger_name.rfind('.'); + + const auto& parent_name = dot_pos == ::std::string::npos ? "" : logger_name.substr(0,dot_pos); + + + if (loggers_.count(parent_name) == 0) { + auto parent_logger = ::std::make_shared<::spdlog::logger>(parent_name); + register_logger_(parent_logger); + } + + loggers_[logger_name]->set_parent(loggers_[parent_name]); + } } // namespace details diff --git a/include/spdlog/logger-inl.h b/include/spdlog/logger-inl.h index 227cec43..a68b8b05 100644 --- a/include/spdlog/logger-inl.h +++ b/include/spdlog/logger-inl.h @@ -23,6 +23,8 @@ SPDLOG_INLINE logger::logger(const logger &other) , flush_level_(other.flush_level_.load(std::memory_order_relaxed)) , custom_err_handler_(other.custom_err_handler_) , tracer_(other.tracer_) + , propagate_(other.propagate_) + , parent_(other.parent_) {} SPDLOG_INLINE logger::logger(logger &&other) SPDLOG_NOEXCEPT : name_(std::move(other.name_)), @@ -30,7 +32,9 @@ SPDLOG_INLINE logger::logger(logger &&other) SPDLOG_NOEXCEPT : name_(std::move(o level_(other.level_.load(std::memory_order_relaxed)), flush_level_(other.flush_level_.load(std::memory_order_relaxed)), custom_err_handler_(std::move(other.custom_err_handler_)), - tracer_(std::move(other.tracer_)) + tracer_(std::move(other.tracer_)), + propagate_(other.propagate_), + parent_(std::move(other.parent_)) {} @@ -43,7 +47,11 @@ SPDLOG_INLINE logger &logger::operator=(logger other) SPDLOG_NOEXCEPT SPDLOG_INLINE void logger::swap(spdlog::logger &other) SPDLOG_NOEXCEPT { name_.swap(other.name_); + bool other_propagate = other.propagate_; + other.propagate_ = propagate_; + propagate_ = other_propagate; sinks_.swap(other.sinks_); + parent_.swap(other.parent_); // swap level_ auto other_level = other.level_.load(); @@ -69,6 +77,12 @@ SPDLOG_INLINE void logger::set_level(level::level_enum log_level) level_.store(log_level); } + +SPDLOG_INLINE void logger::set_propagate(bool propagate) +{ + propagate_ = propagate; +} + SPDLOG_INLINE level::level_enum logger::level() const { return static_cast(level_.load(std::memory_order_relaxed)); @@ -79,6 +93,16 @@ SPDLOG_INLINE const std::string &logger::name() const return name_; } +SPDLOG_INLINE bool logger::propagate() const +{ + return propagate_; +} + +SPDLOG_INLINE std::shared_ptr logger::parent() const +{ + return parent_; +} + // set formatting for the sinks in this logger. // each sink will get a separate instance of the formatter object. SPDLOG_INLINE void logger::set_formatter(std::unique_ptr f) @@ -169,6 +193,17 @@ SPDLOG_INLINE void logger::log_it_(const spdlog::details::log_msg &log_msg, bool { sink_it_(log_msg); } + + auto tmp_lgr = this; + + while (tmp_lgr->propagate_ && tmp_lgr->parent_ != nullptr) { + tmp_lgr = tmp_lgr->parent_.get(); + if (tmp_lgr->should_log(log_msg.level)) + { + tmp_lgr->sink_it_(log_msg); + } + } + if (traceback_enabled) { tracer_.push_back(log_msg); @@ -205,6 +240,9 @@ SPDLOG_INLINE void logger::flush_() } SPDLOG_LOGGER_CATCH(source_loc()) } + if (parent_) { + parent_->flush_(); + } } SPDLOG_INLINE void logger::dump_backtrace_() diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h index 0802a5d9..2b782c02 100644 --- a/include/spdlog/logger.h +++ b/include/spdlog/logger.h @@ -51,6 +51,10 @@ namespace spdlog { +namespace details { + class registry; +} + class SPDLOG_API logger { public: @@ -126,7 +130,7 @@ public: { bool log_enabled = should_log(lvl); bool traceback_enabled = tracer_.enabled(); - if (!log_enabled && !traceback_enabled) + if (!propagate_ && !log_enabled && !traceback_enabled) { return; } @@ -315,6 +319,14 @@ public: const std::string &name() const; + // Indicates the value of the propagate property + bool propagate() const; + // Sets the propagate property. Only if propagate is true, log messages are propagated through the hierarchy + void set_propagate(bool); + + // Gets the hierarchical parent of this logger. + std::shared_ptr parent() const; + // set formatting for the sinks in this logger. // each sink will get a separate instance of the formatter object. void set_formatter(std::unique_ptr f); @@ -354,6 +366,9 @@ protected: spdlog::level_t flush_level_{level::off}; err_handler custom_err_handler_{nullptr}; details::backtracer tracer_; + bool propagate_{true}; + + std::shared_ptr parent_; // common implementation for after templated public api has been resolved template @@ -416,6 +431,13 @@ protected: // handle errors during logging. // default handler prints the error to stderr at max rate of 1 message/sec. void err_handler_(const std::string &msg); + + void set_parent(std::shared_ptr parent_logger) { + parent_ = parent_logger; + } + + // friend class declaration for registry, since it needs to set parent of logger + friend class spdlog::details::registry; }; void swap(logger &a, logger &b); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 942ec519..09968b8a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,7 +44,8 @@ set(SPDLOG_UTESTS_SOURCES test_custom_callbacks.cpp test_cfg.cpp test_time_point.cpp - test_stopwatch.cpp) + test_stopwatch.cpp + test_hierarchical.cpp) if(NOT SPDLOG_NO_EXCEPTIONS) list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp) diff --git a/tests/test_hierarchical.cpp b/tests/test_hierarchical.cpp new file mode 100644 index 00000000..126911c8 --- /dev/null +++ b/tests/test_hierarchical.cpp @@ -0,0 +1,79 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/async.h" + +TEST_CASE("hierarchical1", "[hierarchical]") +{ + + using spdlog::sinks::test_sink_st; + auto test_sink_root = std::make_shared(); + auto test_sink_lvl1 = std::make_shared(); + auto test_sink_lvl3 = std::make_shared(); + auto test_sink_lvl4 = std::make_shared(); + auto test_sink_lvl5 = std::make_shared(); + + auto logger_root = std::make_shared("", test_sink_root); + logger_root->set_pattern("root - %v"); + logger_root->set_level(spdlog::level::info); + spdlog::set_default_logger(logger_root); + + + auto logger_1 = std::make_shared("1", test_sink_lvl1); + spdlog::register_logger(logger_1); + logger_1->set_pattern("1 - %v"); + logger_1->set_level(spdlog::level::debug); + // no sink for logger 2 + auto logger_2 = std::make_shared("1.2"); + spdlog::register_logger(logger_2); + logger_2->set_level(spdlog::level::info); + auto logger_3 = std::make_shared("1.2.3", test_sink_lvl3); + spdlog::register_logger(logger_3); + logger_3->set_pattern("3 - %v"); + logger_3->set_level(spdlog::level::warn); + auto logger_4 = std::make_shared("1.2.3.4", test_sink_lvl4); + spdlog::register_logger(logger_4); + logger_4->set_pattern("4 - %v"); + logger_4->set_level(spdlog::level::err); + // Has propagate false, therefore should not propagate messages to higher loggers + auto logger_5 = std::make_shared("1.2.3.4.5", test_sink_lvl5); + spdlog::register_logger(logger_5); + logger_5->set_pattern("5 - %v"); + logger_5->set_level(spdlog::level::debug); + logger_5->set_propagate(false); + + logger_4->error("error 4"); + logger_4->info("info 4"); + logger_4->debug("debug 4"); + logger_4->warn("warn 4"); + + logger_2->error("error 2"); + logger_2->warn("warn 2"); + + logger_5->error("error 5"); + + REQUIRE(test_sink_root->lines().size() == 5); + REQUIRE(test_sink_root->lines()[0] == "root - error 4"); + REQUIRE(test_sink_root->lines()[1] == "root - info 4"); + REQUIRE(test_sink_root->lines()[2] == "root - warn 4"); + REQUIRE(test_sink_root->lines()[3] == "root - error 2"); + REQUIRE(test_sink_root->lines()[4] == "root - warn 2"); + + REQUIRE(test_sink_lvl1->lines().size() == 6); + REQUIRE(test_sink_lvl1->lines()[0] == "1 - error 4"); + REQUIRE(test_sink_lvl1->lines()[1] == "1 - info 4"); + REQUIRE(test_sink_lvl1->lines()[2] == "1 - debug 4"); + REQUIRE(test_sink_lvl1->lines()[3] == "1 - warn 4"); + REQUIRE(test_sink_lvl1->lines()[4] == "1 - error 2"); + REQUIRE(test_sink_lvl1->lines()[5] == "1 - warn 2"); + + REQUIRE(test_sink_lvl3->lines().size() == 2); + REQUIRE(test_sink_lvl3->lines()[0] == "3 - error 4"); + REQUIRE(test_sink_lvl3->lines()[1] == "3 - warn 4"); + + REQUIRE(test_sink_lvl4->lines().size() == 1); + REQUIRE(test_sink_lvl4->lines()[0] == "4 - error 4"); + + REQUIRE(test_sink_lvl5->lines().size() == 1); + REQUIRE(test_sink_lvl5->lines()[0] == "5 - error 5"); + +} diff --git a/tests/test_registry.cpp b/tests/test_registry.cpp index 8e632cc6..335d7195 100644 --- a/tests/test_registry.cpp +++ b/tests/test_registry.cpp @@ -34,7 +34,9 @@ TEST_CASE("apply_all", "[registry]") int counter = 0; spdlog::apply_all([&counter](std::shared_ptr) { counter++; }); - REQUIRE(counter == 2); + // 3 because the root logger with empty name will be automatically created + REQUIRE(counter == 3); + spdlog::drop(""); counter = 0; spdlog::drop(tested_logger_name2);