diff --git a/README.md b/README.md index 6962d324..9cc7e557 100644 --- a/README.md +++ b/README.md @@ -160,11 +160,12 @@ The header files should be installed in `build/install/include`. - `daily_file_sink_mt` - `null_sink_st` - `null_sink_mt` +- `dist_sink_st` +- `dist_sink_mt` - `syslog_sink` (only for Linux, `SPDLOG_ENABLE_SYSLOG` preprocessor definition must be defined before any `spdlog`/`spdlog_setup` header is included) -Currently `ostream_sink` and `dist_sink` do not fit into the use case and are -not supported. +Currently `ostream_sink` does not fit into the use case and is not supported. For more information about how the above sinks work in `spdlog`, please refer to the original `spdlog` sinks wiki page at: diff --git a/include/spdlog_setup/details/conf_impl.h b/include/spdlog_setup/details/conf_impl.h index 8a6a682f..533769a5 100644 --- a/include/spdlog_setup/details/conf_impl.h +++ b/include/spdlog_setup/details/conf_impl.h @@ -26,6 +26,7 @@ #include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/dist_sink.h" #include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/sink.h" @@ -49,6 +50,7 @@ #include #include #include +#include #include #include @@ -132,6 +134,12 @@ enum class sink_type { /** Represents null_sink_mt */ NullSinkMt, + /** Represents dist_sink_st */ + DistSinkSt, + + /** Represents dist_sink_mt */ + DistSinkMt, + /** Represents syslog_sink_st */ SyslogSinkSt, @@ -629,6 +637,8 @@ inline auto sink_type_from_str(const std::string &type) -> sink_type { {"daily_file_sink_mt", sink_type::DailyFileSinkMt}, {"null_sink_st", sink_type::NullSinkSt}, {"null_sink_mt", sink_type::NullSinkMt}, + {"dist_sink_st", sink_type::DistSinkSt}, + {"dist_sink_mt", sink_type::DistSinkMt}, #ifdef SPDLOG_ENABLE_SYSLOG {"syslog_sink_st", sink_type::SyslogSinkSt}, {"syslog_sink_mt", sink_type::SyslogSinkMt}, @@ -886,8 +896,30 @@ auto setup_syslog_sink(const std::shared_ptr &sink_table) #endif +template +auto setup_dist_sink( + const std::shared_ptr &sink_table, + std::vector &ref_sinks) + -> std::shared_ptr { + + using names::SINKS; + + // std + using std::make_shared; + using std::string; + + ref_sinks = array_from_table( + sink_table, + SINKS, + fmt::format("Missing '{}' field of sink names for dist_sink", SINKS)); + + return make_shared(); +} + inline auto sink_from_sink_type( - const sink_type sink_val, const std::shared_ptr &sink_table) + const sink_type sink_val, + const std::shared_ptr &sink_table, + std::vector &ref_sinks) -> std::shared_ptr { // fmt @@ -898,6 +930,8 @@ inline auto sink_from_sink_type( using spdlog::sinks::basic_file_sink_st; using spdlog::sinks::daily_file_sink_mt; using spdlog::sinks::daily_file_sink_st; + using spdlog::sinks::dist_sink_mt; + using spdlog::sinks::dist_sink_st; using spdlog::sinks::null_sink_mt; using spdlog::sinks::null_sink_st; using spdlog::sinks::rotating_file_sink_mt; @@ -977,6 +1011,12 @@ inline auto sink_from_sink_type( case sink_type::NullSinkMt: return make_shared(); + case sink_type::DistSinkSt: + return setup_dist_sink(sink_table, ref_sinks); + + case sink_type::DistSinkMt: + return setup_dist_sink(sink_table, ref_sinks); + #ifdef SPDLOG_ENABLE_SYSLOG case sink_type::SyslogSinkSt: return setup_syslog_sink(sink_table); @@ -1031,7 +1071,9 @@ inline void set_logger_flush_level_if_present( }); } -inline auto setup_sink(const std::shared_ptr &sink_table) +inline auto setup_sink( + const std::shared_ptr &sink_table, + std::vector &ref_sinks) -> std::shared_ptr { using names::TYPE; @@ -1047,7 +1089,7 @@ inline auto setup_sink(const std::shared_ptr &sink_table) sink_table, TYPE, format("Sink missing '{}' field", TYPE)); const auto sink_val = sink_type_from_str(type_val); - auto sink = sink_from_sink_type(sink_val, sink_table); + auto sink = sink_from_sink_type(sink_val, sink_table, ref_sinks); // set optional parts and return back the same sink set_sink_level_if_present(sink_table, sink); @@ -1055,6 +1097,76 @@ inline auto setup_sink(const std::shared_ptr &sink_table) return sink; } +inline void check_ref_sinks_no_cycles_dfs( + const std::string &name, + const std::unordered_map> + &ref_sinks_map, + std::unordered_set &visited) { + + // fmt + using fmt::format; + + auto ref_sinks = ref_sinks_map.find(name); + if (ref_sinks == ref_sinks_map.end()) { + throw setup_error(format("Reference to unknown sink '{}'", name)); + } + + for (const auto &ref_sink : ref_sinks->second) { + auto result = visited.insert(ref_sink); + if (result.second) { + check_ref_sinks_no_cycles_dfs(ref_sink, ref_sinks_map, visited); + } else { + throw setup_error( + format("Cyclic reference with sink '{}'", ref_sink)); + } + } +} + +inline void setup_ref_sinks( + std::unordered_map> + &sinks_map, + const std::unordered_map> + &ref_sinks_map) { + + // spdlog + using spdlog::sink_ptr; + using spdlog::sinks::dist_sink_mt; + using spdlog::sinks::dist_sink_st; + + // std + using std::move; + using std::string; + using std::unordered_set; + using std::vector; + + for (const auto &kv : ref_sinks_map) { + unordered_set visited{kv.first}; + check_ref_sinks_no_cycles_dfs(kv.first, ref_sinks_map, visited); + + auto sink = sinks_map[kv.first]; + auto *dist_st = dynamic_cast(sink.get()); + auto *dist_mt = dynamic_cast(sink.get()); + + if (!dist_st && !dist_mt) { + if (kv.second.empty()) + continue; + + throw setup_error( + "Internal error: only dist sinks may connect to other sinks"); + } + + vector sinks; + for (const auto &ref_sink : kv.second) { + sinks.emplace_back(sinks_map[ref_sink]); + } + if (dist_st) { + dist_st->set_sinks(move(sinks)); + } else { + dist_mt->set_sinks(move(sinks)); + } + } +} + inline auto setup_sinks(const std::shared_ptr &config) -> std::unordered_map> { @@ -1068,7 +1180,9 @@ inline auto setup_sinks(const std::shared_ptr &config) using std::move; using std::shared_ptr; using std::string; + using std::unique_ptr; using std::unordered_map; + using std::vector; const auto sinks = config->get_table_array(SINK_TABLE); @@ -1077,6 +1191,7 @@ inline auto setup_sinks(const std::shared_ptr &config) } unordered_map> sinks_map; + unordered_map> ref_sinks_map; for (const auto &sink_table : *sinks) { auto name = value_from_table( @@ -1084,8 +1199,11 @@ inline auto setup_sinks(const std::shared_ptr &config) NAME, format("One of the sinks does not have a '{}' field", NAME)); + auto &ref_sinks = ref_sinks_map[name]; auto sink = add_msg_on_err( - [&sink_table] { return setup_sink(sink_table); }, + [&sink_table, &ref_sinks] { + return setup_sink(sink_table, ref_sinks); + }, [&name](const string &err_msg) { return format("Sink '{}' error:\n > {}", name, err_msg); }); @@ -1093,6 +1211,8 @@ inline auto setup_sinks(const std::shared_ptr &config) sinks_map.emplace(move(name), move(sink)); } + setup_ref_sinks(sinks_map, ref_sinks_map); + return sinks_map; } diff --git a/src/unit_test/loggers.cpp b/src/unit_test/loggers.cpp index d0598a0a..59d42b39 100644 --- a/src/unit_test/loggers.cpp +++ b/src/unit_test/loggers.cpp @@ -127,7 +127,9 @@ TEST_CASE( } TEST_CASE("Parse logger with sink", "[parse_logger_with_sink]") { - auto sink = spdlog_setup::details::setup_sink(generate_stdout_sink_st()); + std::vector ref_sinks; + auto sink = + spdlog_setup::details::setup_sink(generate_stdout_sink_st(), ref_sinks); const auto sinks_map = std::unordered_map>{ {TEST_SINK_NAME, std::move(sink)}}; @@ -145,7 +147,9 @@ TEST_CASE("Parse logger with sink", "[parse_logger_with_sink]") { TEST_CASE( "Parse logger with missing sink", "[parse_logger_with_missing_sink]") { - auto sink = spdlog_setup::details::setup_sink(generate_stdout_sink_st()); + std::vector ref_sinks; + auto sink = + spdlog_setup::details::setup_sink(generate_stdout_sink_st(), ref_sinks); const auto sinks_map = std::unordered_map>{ {"xxx", std::move(sink)}}; diff --git a/src/unit_test/sinks.cpp b/src/unit_test/sinks.cpp index 35614abf..611ff187 100644 --- a/src/unit_test/sinks.cpp +++ b/src/unit_test/sinks.cpp @@ -11,25 +11,65 @@ #include TEST_CASE("Parse stdout sink st", "[parse_generate_stdout_sink_st]") { + std::vector ref_sinks; const auto sink = - spdlog_setup::details::setup_sink(generate_stdout_sink_st()); + spdlog_setup::details::setup_sink(generate_stdout_sink_st(), ref_sinks); REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stdout_sink_st &)); + REQUIRE(ref_sinks.empty()); } TEST_CASE("Parse stderr sink st", "[parse_generate_stderr_sink_st]") { + std::vector ref_sinks; const auto sink = - spdlog_setup::details::setup_sink(generate_stderr_sink_st()); + spdlog_setup::details::setup_sink(generate_stderr_sink_st(), ref_sinks); REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stderr_sink_st &)); + REQUIRE(ref_sinks.empty()); } TEST_CASE("Parse stdout sink mt", "[parse_generate_stdout_sink_mt]") { + std::vector ref_sinks; const auto sink = - spdlog_setup::details::setup_sink(generate_stdout_sink_mt()); + spdlog_setup::details::setup_sink(generate_stdout_sink_mt(), ref_sinks); REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stdout_sink_mt &)); + REQUIRE(ref_sinks.empty()); } TEST_CASE("Parse stderr sink mt", "[parse_generate_stderr_sink_mt]") { + std::vector ref_sinks; const auto sink = - spdlog_setup::details::setup_sink(generate_stderr_sink_mt()); + spdlog_setup::details::setup_sink(generate_stderr_sink_mt(), ref_sinks); REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stderr_sink_mt &)); + REQUIRE(ref_sinks.empty()); +} + +TEST_CASE("Parse dist sink st", "[parse_generate_dist_sink_st]") { + std::vector ref_sinks; + const auto sink = + spdlog_setup::details::setup_sink(generate_dist_sink_st(), ref_sinks); + REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::dist_sink_st &)); + REQUIRE_THAT(ref_sinks, Catch::Equals({"sink1", "sink2"})); +} + +TEST_CASE("Parse dist sink mt", "[parse_generate_dist_sink_mt]") { + std::vector ref_sinks; + const auto sink = + spdlog_setup::details::setup_sink(generate_dist_sink_mt(), ref_sinks); + REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::dist_sink_mt &)); + REQUIRE_THAT(ref_sinks, Catch::Equals({"sink1", "sink2"})); +} + +TEST_CASE("Parse dist sink circular", "[parse_generate_dist_sink_circular]") { + REQUIRE_THROWS_AS( + spdlog_setup::details::setup_sinks(generate_dist_sink_circular()), + spdlog_setup::setup_error); +} + +TEST_CASE("Parse dist ref sinks", "[parse_generate_dist_sink_refs]") { + auto sinks_map = + spdlog_setup::details::setup_sinks(generate_dist_sink_with_refs()); + auto sink = sinks_map["dist_sink"]; + REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::dist_sink_mt &)); + // Can't check the dist sink's wiring because there's no getter for its + // subordinate sinks, but setup_sinks returning normally rather than + // exceptionally is enough } diff --git a/src/unit_test/sinks.h b/src/unit_test/sinks.h index 13c2c2d9..715e0627 100644 --- a/src/unit_test/sinks.h +++ b/src/unit_test/sinks.h @@ -44,3 +44,80 @@ inline auto generate_stderr_sink_mt() -> std::shared_ptr { sink_table->insert(names::TYPE, std::string("stderr_sink_mt")); return std::move(sink_table); } + +inline auto generate_dist_sink(std::string type) + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto ref_sinks = cpptoml::make_array(); + ref_sinks->push_back(std::string("sink1")); + ref_sinks->push_back(std::string("sink2")); + + auto sink_table = cpptoml::make_table(); + sink_table->insert(names::TYPE, std::move(type)); + sink_table->insert(names::SINKS, ref_sinks); + return std::move(sink_table); +} + +inline auto generate_dist_sink_st() -> std::shared_ptr { + return generate_dist_sink("dist_sink_st"); +} + +inline auto generate_dist_sink_mt() -> std::shared_ptr { + return generate_dist_sink("dist_sink_mt"); +} + +inline auto generate_dist_sink_circular() -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto dist_sink1 = cpptoml::make_table(); + dist_sink1->insert(names::NAME, std::string("sink1")); + dist_sink1->insert(names::TYPE, std::string("dist_sink_st")); + auto dist_sink1_sinks = cpptoml::make_array(); + dist_sink1_sinks->push_back(std::string("sink2")); + dist_sink1->insert(names::SINKS, dist_sink1_sinks); + + auto dist_sink2 = cpptoml::make_table(); + dist_sink2->insert(names::NAME, std::string("sink2")); + dist_sink2->insert(names::TYPE, std::string("dist_sink_mt")); + auto dist_sink2_sinks = cpptoml::make_array(); + dist_sink2_sinks->push_back(std::string("sink1")); + dist_sink2->insert(names::SINKS, dist_sink2_sinks); + + auto sink_table = cpptoml::make_table_array(); + sink_table->push_back(dist_sink1); + sink_table->push_back(dist_sink2); + + auto config = cpptoml::make_table(); + config->insert(names::SINK_TABLE, sink_table); + return std::move(config); +} + +inline auto generate_dist_sink_with_refs() -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto dist_sink = cpptoml::make_table(); + dist_sink->insert(names::NAME, std::string("dist_sink")); + dist_sink->insert(names::TYPE, std::string("dist_sink_mt")); + auto ref_sinks = cpptoml::make_array(); + ref_sinks->push_back(std::string("sink1")); + ref_sinks->push_back(std::string("sink2")); + dist_sink->insert(names::SINKS, ref_sinks); + + auto sink1 = cpptoml::make_table(); + sink1->insert(names::NAME, std::string("sink1")); + sink1->insert(names::TYPE, std::string("null_sink_mt")); + + auto sink2 = cpptoml::make_table(); + sink2->insert(names::NAME, std::string("sink2")); + sink2->insert(names::TYPE, std::string("null_sink_mt")); + + auto sink_table = cpptoml::make_table_array(); + sink_table->push_back(dist_sink); + sink_table->push_back(sink1); + sink_table->push_back(sink2); + + auto config = cpptoml::make_table(); + config->insert(names::SINK_TABLE, sink_table); + return std::move(config); +}