feat: add hierarchical logger support

pull/2803/head
Stefan Profanter 2 years ago
parent 4b8ff51a29
commit 58db7c1143
No known key found for this signature in database
GPG Key ID: 9FAB226B0CD36002

@ -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<spdlog::pattern_formatter>("root [%n] [%^%l%$] %v"));
auto lvl1 = spdlog::stdout_color_mt("propagate_lvl1");
lvl1->set_formatter(spdlog::details::make_unique<spdlog::pattern_formatter>("lvl1 [%n] [%^%l%$] %v"));
lvl1->set_level(spdlog::level::debug);
auto lvl2 = std::make_shared<spdlog::logger>("propagate_lvl1.lvl2");
spdlog::register_logger(lvl2);
// skip level 3
auto lvl4 = std::make_shared<spdlog::logger>("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<spdlog::pattern_formatter>("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<spdlog::pattern_formatter>("lvl2 [%n] [%^%l%$] %v"));
// skip level 3
auto multi_lvl4 = std::make_shared<spdlog::logger>("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");
}

@ -219,6 +219,18 @@ SPDLOG_INLINE void registry::flush_all()
SPDLOG_INLINE void registry::drop(const std::string &logger_name)
{
std::lock_guard<std::mutex> 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<logger> 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

@ -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::level_enum>(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<spdlog::logger> 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<formatter> 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_()

@ -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<spdlog::logger> 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<formatter> 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<spdlog::logger> parent_;
// common implementation for after templated public api has been resolved
template<typename... Args>
@ -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<spdlog::logger> 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);

@ -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)

@ -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<test_sink_st>();
auto test_sink_lvl1 = std::make_shared<test_sink_st>();
auto test_sink_lvl3 = std::make_shared<test_sink_st>();
auto test_sink_lvl4 = std::make_shared<test_sink_st>();
auto test_sink_lvl5 = std::make_shared<test_sink_st>();
auto logger_root = std::make_shared<spdlog::logger>("", 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<spdlog::logger>("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<spdlog::logger>("1.2");
spdlog::register_logger(logger_2);
logger_2->set_level(spdlog::level::info);
auto logger_3 = std::make_shared<spdlog::logger>("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<spdlog::logger>("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<spdlog::logger>("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");
}

@ -34,7 +34,9 @@ TEST_CASE("apply_all", "[registry]")
int counter = 0;
spdlog::apply_all([&counter](std::shared_ptr<spdlog::logger>) { 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);

Loading…
Cancel
Save