diff --git a/CHANGELOG.md b/CHANGELOG.md index 24dea7e4..ec0fa1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Add support for async logger. This fixes + ([#44](https://github.com/guangie88/spdlog_setup/issues/44)) + - This makes logger accepts `type`, which takes in `sync` or `async`. + `sync` is default. Refer to the README for more details. - Change support to `spdlog` `v1.y.z` tag release, currently tested all `v1.0.0` to `v1.3.1` to be working. This fixes ([#26](https://github.com/guangie88/spdlog_setup/issues/26)). diff --git a/CMakeLists.txt b/CMakeLists.txt index cd6482bd..3ef28918 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,9 +99,10 @@ install( endif() # spdlog_setup_unit_test +FILE(GLOB unit_test_cpps src/unit_test/*.cpp) if(SPDLOG_SETUP_INCLUDE_UNIT_TESTS) add_executable(spdlog_setup_unit_test - src/unit_test/unit_test.cpp) + ${unit_test_cpps}) set_property(TARGET spdlog_setup_unit_test PROPERTY CXX_STANDARD 11) diff --git a/README.md b/README.md index be5d412e..978da87f 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,31 @@ level = "trace" name = "console" sinks = ["console_st", "console_mt"] pattern = "succient" + +# Async + +[global_thread_pool] +queue_size = 8192 +num_threads = 1 + +[[thread_pool]] +name = "tp" +queue_size = 4096 +num_threads = 2 + +[[logger]] +type = "async" +name = "global_async" +sinks = ["console_mt"] +pattern = "succient" + +[[logger]] +type = "async" +name = "local_async" +sinks = ["console_mt"] +pattern = "succient" +thread_pool = "tp" +overflow_policy = "overrun_oldest" # block (default) | overrun_oldest ``` ### Tagged-Base Pre-TOML File Configuration diff --git a/config/log_conf_linux.toml b/config/log_conf_linux.toml index 4d1152f5..0e279dc5 100644 --- a/config/log_conf_linux.toml +++ b/config/log_conf_linux.toml @@ -125,3 +125,28 @@ level = "trace" name = "console" sinks = ["console_st", "console_mt"] pattern = "succient" + +# Async + +[global_thread_pool] +queue_size = 8192 +num_threads = 1 + +[[thread_pool]] +name = "tp" +queue_size = 4096 +num_threads = 2 + +[[logger]] +type = "async" +name = "global_async" +sinks = ["console_mt"] +pattern = "succient" + +[[logger]] +type = "async" +name = "local_async" +sinks = ["console_mt"] +pattern = "succient" +thread_pool = "tp" +overflow_policy = "overrun_oldest" # block (default) | overrun_oldest diff --git a/config/log_conf_win.toml b/config/log_conf_win.toml index 0165bede..d97e3e71 100644 --- a/config/log_conf_win.toml +++ b/config/log_conf_win.toml @@ -13,6 +13,20 @@ # check out https://github.com/gabime/spdlog/wiki/3.-Custom-formatting global_pattern = "[%Y-%m-%dT%T%z] [%L] <%n>: %v" +[global_thread_pool] +queue_size = 8192 +num_threads = 1 + +[[thread_pool]] +name = "tp_1" +queue_size = 4096 +num_threads = 2 + +[[thread_pool]] +name = "tp_2" +queue_size = 2048 +num_threads = 4 + [[sink]] name = "console_st" type = "stdout_sink_st" @@ -106,3 +120,28 @@ level = "trace" name = "console" sinks = ["console_st", "console_mt"] pattern = "succient" + +# Async + +[global_thread_pool] +queue_size = 8192 +num_threads = 1 + +[[thread_pool]] +name = "tp" +queue_size = 4096 +num_threads = 2 + +[[logger]] +type = "async" +name = "global_async" +sinks = ["console_mt"] +pattern = "succient" + +[[logger]] +type = "async" +name = "local_async" +sinks = ["console_mt"] +pattern = "succient" +thread_pool = "tp" +overflow_policy = "overrun_oldest" # block (default) | overrun_oldest diff --git a/include/spdlog_setup/conf.h b/include/spdlog_setup/conf.h index 5da942f7..3152742c 100644 --- a/include/spdlog_setup/conf.h +++ b/include/spdlog_setup/conf.h @@ -110,7 +110,7 @@ void from_file_with_tag_replacement( cpptoml::parser parser(toml_ss); const auto config = parser.parse(); - details::setup_impl(config); + details::setup(config); } catch (const setup_error &) { throw; } catch (const exception &e) { @@ -158,7 +158,7 @@ auto from_file_and_override_with_tag_replacement( details::merge_config_root(merged_config, override_config); } - details::setup_impl(merged_config); + details::setup(merged_config); return has_override; } catch (const setup_error &) { throw; @@ -174,7 +174,7 @@ inline void from_file(const std::string &toml_path) { try { const auto config = cpptoml::parse_file(toml_path); - details::setup_impl(config); + details::setup(config); } catch (const exception &e) { throw setup_error(e.what()); } @@ -205,7 +205,7 @@ inline auto from_file_and_override( details::merge_config_root(merged_config, override_config); } - details::setup_impl(merged_config); + details::setup(merged_config); return has_override; } catch (const exception &e) { throw setup_error(e.what()); diff --git a/include/spdlog_setup/details/conf_impl.h b/include/spdlog_setup/details/conf_impl.h index eaf1a825..4491bc85 100644 --- a/include/spdlog_setup/details/conf_impl.h +++ b/include/spdlog_setup/details/conf_impl.h @@ -19,6 +19,9 @@ // Just so that it works for v1.3.0 #include "spdlog/spdlog.h" +// To support all asynchronous loggers +#include "spdlog/async.h" + #include "spdlog/fmt/fmt.h" #include "spdlog/sinks/basic_file_sink.h" @@ -117,14 +120,36 @@ enum class sink_type { SyslogSinkMt, }; +/** + * Describes the logger sync types in enumeration form. + */ +enum class sync_type { + /** Represent sync type */ + Sync, + + /** Represent async type */ + Async, +}; + +namespace defaults { +static constexpr auto ASYNC_OVERFLOW_POLICY = + spdlog::async_overflow_policy::block; +static constexpr auto THREAD_POOL_QUEUE_SIZE = 8192; +static constexpr auto THREAD_POOL_NUM_THREADS = 1; +} // namespace defaults + namespace names { // table names +static constexpr auto GLOBAL_THREAD_POOL_TABLE = "global_thread_pool"; static constexpr auto LOGGER_TABLE = "logger"; static constexpr auto PATTERN_TABLE = "pattern"; static constexpr auto SINK_TABLE = "sink"; +static constexpr auto THREAD_POOL_TABLE = "thread_pool"; // field names +static constexpr auto ASYNC = "async"; static constexpr auto BASE_FILENAME = "base_filename"; +static constexpr auto BLOCK = "block"; static constexpr auto CREATE_PARENT_DIR = "create_parent_dir"; static constexpr auto FILENAME = "filename"; static constexpr auto GLOBAL_PATTERN = "global_pattern"; @@ -133,17 +158,34 @@ static constexpr auto LEVEL = "level"; static constexpr auto MAX_FILES = "max_files"; static constexpr auto MAX_SIZE = "max_size"; static constexpr auto NAME = "name"; +static constexpr auto NUM_THREADS = "num_threads"; +static constexpr auto OVERRUN_OLDEST = "overrun_oldest"; +static constexpr auto OVERFLOW_POLICY = "overflow_policy"; static constexpr auto PATTERN = "pattern"; +static constexpr auto QUEUE_SIZE = "queue_size"; static constexpr auto ROTATION_HOUR = "rotation_hour"; static constexpr auto ROTATION_MINUTE = "rotation_minute"; static constexpr auto SINKS = "sinks"; +static constexpr auto SYNC = "sync"; static constexpr auto SYSLOG_FACILITY = "syslog_facility"; static constexpr auto SYSLOG_OPTION = "syslog_option"; +static constexpr auto THREAD_POOL = "thread_pool"; static constexpr auto TRUNCATE = "truncate"; static constexpr auto TYPE = "type"; static constexpr auto VALUE = "value"; } // namespace names +const std::unordered_map SYNC_MAP{{ + {names::SYNC, sync_type::Sync}, + {names::ASYNC, sync_type::Async}, +}}; + +const std::unordered_map + ASYNC_OVERFLOW_POLICY_MAP{{ + {names::BLOCK, spdlog::async_overflow_policy::block}, + {names::OVERRUN_OLDEST, spdlog::async_overflow_policy::overrun_oldest}, + }}; + inline auto get_parent_path(const std::string &file_path) -> std::string { // string using std::string; @@ -657,8 +699,7 @@ inline void set_sink_level_if_present( } template -auto basic_file_sink_from_table( - const std::shared_ptr &sink_table) +auto setup_basic_file_sink(const std::shared_ptr &sink_table) -> std::shared_ptr { using names::FILENAME; @@ -691,8 +732,7 @@ auto basic_file_sink_from_table( } template -auto rotating_file_sink_from_table( - const std::shared_ptr &sink_table) +auto setup_rotating_file_sink(const std::shared_ptr &sink_table) -> std::shared_ptr { using names::BASE_FILENAME; @@ -737,8 +777,7 @@ auto rotating_file_sink_from_table( } template -auto daily_file_sink_from_table( - const std::shared_ptr &sink_table) +auto setup_daily_file_sink(const std::shared_ptr &sink_table) -> std::shared_ptr { using names::BASE_FILENAME; @@ -783,7 +822,7 @@ auto daily_file_sink_from_table( #ifdef SPDLOG_ENABLE_SYSLOG template -auto syslog_sink_from_table(const std::shared_ptr &sink_table) +auto setup_syslog_sink(const std::shared_ptr &sink_table) -> std::shared_ptr { using names::IDENT; @@ -864,22 +903,22 @@ inline auto sink_from_sink_type( return make_shared(); case sink_type::BasicFileSinkSt: - return basic_file_sink_from_table(sink_table); + return setup_basic_file_sink(sink_table); case sink_type::BasicFileSinkMt: - return basic_file_sink_from_table(sink_table); + return setup_basic_file_sink(sink_table); case sink_type::RotatingFileSinkSt: - return rotating_file_sink_from_table(sink_table); + return setup_rotating_file_sink(sink_table); case sink_type::RotatingFileSinkMt: - return rotating_file_sink_from_table(sink_table); + return setup_rotating_file_sink(sink_table); case sink_type::DailyFileSinkSt: - return daily_file_sink_from_table(sink_table); + return setup_daily_file_sink(sink_table); case sink_type::DailyFileSinkMt: - return daily_file_sink_from_table(sink_table); + return setup_daily_file_sink(sink_table); case sink_type::NullSinkSt: return make_shared(); @@ -889,10 +928,10 @@ inline auto sink_from_sink_type( #ifdef SPDLOG_ENABLE_SYSLOG case sink_type::SyslogSinkSt: - return syslog_sink_from_table(sink_table); + return setup_syslog_sink(sink_table); case sink_type::SyslogSinkMt: - return syslog_sink_from_table(sink_table); + return setup_syslog_sink(sink_table); #endif default: @@ -902,7 +941,23 @@ inline auto sink_from_sink_type( } } -inline auto sink_from_table(const std::shared_ptr &sink_table) +inline void set_logger_level_if_present( + const std::shared_ptr &logger_table, + const std::shared_ptr &logger) { + + using names::LEVEL; + + // std + using std::string; + + if_value_from_table( + logger_table, LEVEL, [&logger](const string &level) { + const auto level_enum = level_from_str(level); + logger->set_level(level_enum); + }); +} + +inline auto setup_sink(const std::shared_ptr &sink_table) -> std::shared_ptr { using names::TYPE; @@ -926,23 +981,7 @@ inline auto sink_from_table(const std::shared_ptr &sink_table) return sink; } -inline void set_logger_level_if_present( - const std::shared_ptr &logger_table, - const std::shared_ptr &logger) { - - using names::LEVEL; - - // std - using std::string; - - if_value_from_table( - logger_table, LEVEL, [&logger](const string &level) { - const auto level_enum = level_from_str(level); - logger->set_level(level_enum); - }); -} - -inline auto setup_sinks_impl(const std::shared_ptr &config) +inline auto setup_sinks(const std::shared_ptr &config) -> std::unordered_map> { using names::NAME; @@ -972,7 +1011,7 @@ inline auto setup_sinks_impl(const std::shared_ptr &config) format("One of the sinks does not have a '{}' field", NAME)); auto sink = add_msg_on_err( - [&sink_table] { return sink_from_table(sink_table); }, + [&sink_table] { return setup_sink(sink_table); }, [&name](const string &err_msg) { return format("Sink '{}' error:\n > {}", name, err_msg); }); @@ -983,7 +1022,7 @@ inline auto setup_sinks_impl(const std::shared_ptr &config) return sinks_map; } -inline auto setup_formats_impl(const std::shared_ptr &config) +inline auto setup_patterns(const std::shared_ptr &config) -> std::unordered_map { using names::NAME; @@ -1001,19 +1040,19 @@ inline auto setup_formats_impl(const std::shared_ptr &config) // possible to return an entire empty pattern map unordered_map patterns_map; - const auto formats = config->get_table_array(PATTERN_TABLE); + const auto patterns = config->get_table_array(PATTERN_TABLE); - if (formats) { - for (const auto &format_table : *formats) { + if (patterns) { + for (const auto &pattern_table : *patterns) { auto name = value_from_table( - format_table, + pattern_table, NAME, - format("One of the formats does not have a '{}' field", NAME)); + format("One of the patterns does not have a '{}' field", NAME)); auto value = value_from_table( - format_table, + pattern_table, VALUE, - format("Format '{}' does not have '{}' field", name, VALUE)); + format("Pattern '{}' does not have '{}' field", name, VALUE)); patterns_map.emplace(move(name), move(value)); } @@ -1022,17 +1061,224 @@ inline auto setup_formats_impl(const std::shared_ptr &config) return patterns_map; } -inline void setup_loggers_impl( +inline auto +setup_thread_pools(const std::shared_ptr &config) -> std:: + unordered_map> { + + using names::GLOBAL_THREAD_POOL_TABLE; + using names::NAME; + using names::NUM_THREADS; + using names::QUEUE_SIZE; + using names::THREAD_POOL_TABLE; + + // fmt + using fmt::format; + + // spdlog + using spdlog::init_thread_pool; + using spdlog::details::thread_pool; + + // std + using std::make_shared; + using std::move; + using std::shared_ptr; + using std::string; + using std::unordered_map; + + // reinitialize global thread pool for async loggers if present + const auto global_thread_pool_table = + config->get_table(GLOBAL_THREAD_POOL_TABLE); + + if (global_thread_pool_table) { + const auto queue_size = value_from_table_or( + global_thread_pool_table, + QUEUE_SIZE, + defaults::THREAD_POOL_QUEUE_SIZE); + + const auto num_threads = value_from_table_or( + global_thread_pool_table, + NUM_THREADS, + defaults::THREAD_POOL_NUM_THREADS); + + init_thread_pool(queue_size, num_threads); + } + + // possible to return an entire empty thread pools map + unordered_map> thread_pools_map; + + const auto thread_pools = config->get_table_array(THREAD_POOL_TABLE); + + if (thread_pools) { + for (const auto &thread_pool_table : *thread_pools) { + auto name = value_from_table( + thread_pool_table, + NAME, + format( + "One of the thread pools does not have a '{}' field", + NAME)); + + const auto queue_size = value_from_table( + thread_pool_table, + QUEUE_SIZE, + format( + "Thread pool '{}' does not have '{}' field", + name, + QUEUE_SIZE)); + + const auto num_threads = value_from_table( + thread_pool_table, + NUM_THREADS, + format( + "Thread pool '{}' does not have '{}' field", + name, + NUM_THREADS)); + + thread_pools_map.emplace( + move(name), make_shared(queue_size, num_threads)); + } + } + + return thread_pools_map; +} + +inline auto setup_sync_logger( + const std::string &name, + const std::vector> &logger_sinks) + -> std::shared_ptr { + return std::make_shared( + name, logger_sinks.cbegin(), logger_sinks.cend()); +} + +inline auto setup_async_logger( + const std::string &name, + const std::shared_ptr &logger_table, + const std::vector> &logger_sinks, + const std::unordered_map< + std::string, + std::shared_ptr> &thread_pools_map) + -> std::shared_ptr { + const auto &thread_pool_name_opt = + value_from_table_opt(logger_table, names::THREAD_POOL); + + auto thread_pool = static_cast(thread_pool_name_opt) + ? [&name, &thread_pools_map, &thread_pool_name_opt]() { + const auto &thread_pool_name = *thread_pool_name_opt; + + return find_value_from_map( + thread_pools_map, + thread_pool_name, + fmt::format( + "Unable to find thread pool '{}' for logger '{}'", + thread_pool_name, + name)); + }() + : spdlog::thread_pool(); + + const auto &async_overflow_policy_raw_opt = + value_from_table_opt(logger_table, names::OVERFLOW_POLICY); + + const auto async_overflow_policy = static_cast(async_overflow_policy_raw_opt) + ? [&async_overflow_policy_raw_opt, &name]() { + const auto &async_overflow_policy_raw = *async_overflow_policy_raw_opt; + + return find_value_from_map( + ASYNC_OVERFLOW_POLICY_MAP, + async_overflow_policy_raw, + fmt::format( + "Invalid async overflow policy type given '{}' for logger '{}'", + async_overflow_policy_raw, + name) + ); + }() + : defaults::ASYNC_OVERFLOW_POLICY; + + return std::make_shared( + name, + logger_sinks.cbegin(), + logger_sinks.cend(), + std::move(thread_pool), + async_overflow_policy); +} + +inline auto setup_logger( + const std::shared_ptr &logger_table, + const std::unordered_map> + &sinks_map, + const std::unordered_map< + std::string, + std::shared_ptr> &thread_pools_map) + -> std::shared_ptr { + const auto name = value_from_table( + logger_table, + names::NAME, + fmt::format( + "One of the loggers does not have a '{}' field", names::NAME)); + + const auto sinks = array_from_table( + logger_table, + names::SINKS, + fmt::format( + "Logger '{}' does not have a '{}' field of sink names", + name, + names::SINKS)); + + std::vector> logger_sinks; + logger_sinks.reserve(sinks.size()); + + for (const auto &sink_name : sinks) { + auto sink = find_value_from_map( + sinks_map, + sink_name, + fmt::format( + "Unable to find sink '{}' for logger '{}'", sink_name, name)); + + logger_sinks.push_back(move(sink)); + } + + const auto &sync_raw_opt = + value_from_table_opt(logger_table, names::TYPE); + + const auto sync = + sync_raw_opt ? find_value_from_map( + SYNC_MAP, + *sync_raw_opt, + fmt::format( + "Invalid sync type given '{}' for logger '{}'", + *sync_raw_opt, + name)) + : sync_type::Sync; + + switch (sync) { + case sync_type::Sync: + return setup_sync_logger(name, logger_sinks); + + case sync_type::Async: + return setup_async_logger( + name, logger_table, logger_sinks, thread_pools_map); + + default: + throw setup_error("Reached a buggy scenario of sync_type not fully " + "pattern matched. Please raise an issue."); + } +} + +inline void setup_loggers( const std::shared_ptr &config, const std::unordered_map> &sinks_map, - const std::unordered_map &patterns_map) { + const std::unordered_map &patterns_map, + const std::unordered_map< + std::string, + std::shared_ptr> &thread_pools_map) { using names::GLOBAL_PATTERN; using names::LOGGER_TABLE; using names::NAME; + using names::OVERFLOW_POLICY; using names::PATTERN; using names::SINKS; + using names::THREAD_POOL; + using names::TYPE; // fmt using fmt::format; @@ -1052,49 +1298,23 @@ inline void setup_loggers_impl( } // set up possibly the global pattern if present - auto global_pattern_opt = + const auto global_pattern_opt = value_from_table_opt(config, GLOBAL_PATTERN); for (const auto &logger_table : *loggers) { - const auto name = value_from_table( - logger_table, - NAME, - format("One of the loggers does not have a '{}' field", NAME)); - - const auto sinks = array_from_table( - logger_table, - SINKS, - format( - "Logger '{}' does not have a '{}' field of sink names", - name, - SINKS)); - - vector> logger_sinks; - logger_sinks.reserve(sinks.size()); - - for (const auto &sink_name : sinks) { - auto sink = find_value_from_map( - sinks_map, - sink_name, - format( - "Unable to find sink '{}' for logger '{}'", - sink_name, - name)); - - logger_sinks.push_back(move(sink)); - } - - const auto logger = make_shared( - name, logger_sinks.cbegin(), logger_sinks.cend()); + const auto logger = + setup_logger(logger_table, sinks_map, thread_pools_map); // optional fields add_msg_on_err( [&logger_table, &logger] { set_logger_level_if_present(logger_table, logger); }, - [&name](const string &err_msg) { + [&logger](const string &err_msg) { return format( - "Logger '{}' set level error:\n > {}", name, err_msg); + "Logger '{}' set level error:\n > {}", + logger->name(), + err_msg); }); const auto pattern_name_opt = @@ -1103,7 +1323,7 @@ inline void setup_loggers_impl( using pattern_option_t = cpptoml::option; auto pattern_value_opt = - pattern_name_opt ? [&name, + pattern_name_opt ? [&logger, &patterns_map, &pattern_name_opt]() { const auto &pattern_name = *pattern_name_opt; @@ -1114,13 +1334,15 @@ inline void setup_loggers_impl( format( "Pattern name '{}' cannot be found for logger '{}'", pattern_name, - name)); + logger->name())); return pattern_option_t(pattern_value); }() : pattern_option_t(); + // global_pattern_opt cannot be moved since it needs to be cloned for + // each iteration const auto selected_pattern_opt = pattern_value_opt ? move(pattern_value_opt) : global_pattern_opt; @@ -1130,22 +1352,27 @@ inline void setup_loggers_impl( } } catch (const exception &e) { throw setup_error(format( - "Error setting pattern to logger '{}': {}", name, e.what())); + "Error setting pattern to logger '{}': {}", + logger->name(), + e.what())); } spdlog::register_logger(logger); } } -inline void setup_impl(const std::shared_ptr &config) { +inline void setup(const std::shared_ptr &config) { // set up sinks - const auto sinks_map = setup_sinks_impl(config); + const auto sinks_map = setup_sinks(config); // set up patterns - const auto patterns_map = setup_formats_impl(config); + const auto patterns_map = setup_patterns(config); + + // set up thread pools + const auto thread_pools_map = setup_thread_pools(config); // set up loggers, setting the respective sinks and patterns - setup_loggers_impl(config, sinks_map, patterns_map); + setup_loggers(config, sinks_map, patterns_map, thread_pools_map); } } // namespace details } // namespace spdlog_setup diff --git a/src/unit_test/conf.h b/src/unit_test/conf.h new file mode 100644 index 00000000..9405ee58 --- /dev/null +++ b/src/unit_test/conf.h @@ -0,0 +1,15 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + +#pragma once + +#if defined(unix) || defined(__unix__) || defined(__unix) +#ifndef SPDLOG_ENABLE_SYSLOG +#define SPDLOG_ENABLE_SYSLOG +#endif +#endif + +#include "spdlog_setup/conf.h" diff --git a/src/unit_test/examples.h b/src/unit_test/examples.h index 16570907..292e14c9 100644 --- a/src/unit_test/examples.h +++ b/src/unit_test/examples.h @@ -1,5 +1,13 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + #pragma once +#include "conf.h" + #include #include #include @@ -101,6 +109,29 @@ static constexpr auto FULL_CONF = R"x( name = "console" sinks = ["console_st", "console_mt"] pattern = "succient" + + [global_thread_pool] + queue_size = 8192 + num_threads = 1 + + [[thread_pool]] + name = "tp" + queue_size = 4096 + num_threads = 2 + + [[logger]] + type = "async" + name = "global_async" + sinks = ["console_mt"] + pattern = "succient" + + [[logger]] + type = "async" + name = "local_async" + sinks = ["console_mt"] + pattern = "succient" + thread_pool = "tp" + overflow_policy = "overrun_oldest" # block (default) | overrun_oldest )x"; #else static constexpr auto FULL_CONF = R"x( @@ -204,6 +235,29 @@ static constexpr auto FULL_CONF = R"x( name = "console" sinks = ["console_st", "console_mt"] pattern = "succient" + + [global_thread_pool] + queue_size = 8192 + num_threads = 1 + + [[thread_pool]] + name = "tp" + queue_size = 4096 + num_threads = 2 + + [[logger]] + type = "async" + name = "global_async" + sinks = ["console_mt"] + pattern = "succient" + + [[logger]] + type = "async" + name = "local_async" + sinks = ["console_mt"] + pattern = "succient" + thread_pool = "tp" + overflow_policy = "overrun_oldest" # block (default) | overrun_oldest )x"; #endif diff --git a/src/unit_test/loggers.cpp b/src/unit_test/loggers.cpp new file mode 100644 index 00000000..96d28a08 --- /dev/null +++ b/src/unit_test/loggers.cpp @@ -0,0 +1,125 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + +#include "catch.hpp" + +#include "loggers.h" +#include "sinks.h" + +#include +#include +#include +#include + +const std::unordered_map> + EMPTY_SINKS_MAP; +const std:: + unordered_map> + EMPTY_THREAD_POOLS_MAP; + +TEST_CASE("Parse default logger", "[parse_simple_default_logger]") { + const auto logger = spdlog_setup::details::setup_logger( + generate_simple_default_logger_table(), + EMPTY_SINKS_MAP, + EMPTY_THREAD_POOLS_MAP); + + REQUIRE(logger->name() == TEST_LOGGER_NAME); + REQUIRE(typeid(*logger) == typeid(const spdlog::logger &)); +} + +TEST_CASE("Parse sync logger", "[parse_simple_sync_logger]") { + const auto logger = spdlog_setup::details::setup_logger( + generate_simple_sync_logger_table(), + EMPTY_SINKS_MAP, + EMPTY_THREAD_POOLS_MAP); + + REQUIRE(logger->name() == TEST_LOGGER_NAME); + REQUIRE(typeid(*logger) == typeid(const spdlog::logger &)); +} + +TEST_CASE("Parse async logger", "[parse_simple_async_logger]") { + const auto logger = spdlog_setup::details::setup_logger( + generate_simple_async_logger_table(), + EMPTY_SINKS_MAP, + EMPTY_THREAD_POOLS_MAP); + + REQUIRE(logger->name() == TEST_LOGGER_NAME); + REQUIRE(typeid(*logger) == typeid(const spdlog::async_logger &)); +} + +TEST_CASE("Parse invalid sync logger", "[parse_invalid_sync_logger]") { + REQUIRE_THROWS_AS( + spdlog_setup::details::setup_logger( + generate_invalid_sync_logger_table(), + EMPTY_SINKS_MAP, + EMPTY_THREAD_POOLS_MAP), + spdlog_setup::setup_error); +} + +TEST_CASE( + "Parse async overflow policy block logger", + "[parse_simple_async_overflow_policy_block_logger]") { + const auto logger = spdlog_setup::details::setup_logger( + generate_async_overflow_policy_block_logger_table(), + EMPTY_SINKS_MAP, + EMPTY_THREAD_POOLS_MAP); + + REQUIRE(logger->name() == TEST_LOGGER_NAME); + REQUIRE(typeid(*logger) == typeid(const spdlog::async_logger &)); +} + +TEST_CASE( + "Parse async overflow policy overrun oldest logger", + "[parse_simple_async_overflow_policy_overrun_oldest_logger]") { + const auto logger = spdlog_setup::details::setup_logger( + generate_async_overflow_policy_overrun_oldest_logger_table(), + EMPTY_SINKS_MAP, + EMPTY_THREAD_POOLS_MAP); + + REQUIRE(logger->name() == TEST_LOGGER_NAME); + REQUIRE(typeid(*logger) == typeid(const spdlog::async_logger &)); +} + +TEST_CASE( + "Parse invalid async overflow policy logger", + "[parse_invalid_async_overflow_policy_logger]") { + REQUIRE_THROWS_AS( + spdlog_setup::details::setup_logger( + generate_invalid_async_overflow_policy_logger_table(), + EMPTY_SINKS_MAP, + EMPTY_THREAD_POOLS_MAP), + spdlog_setup::setup_error); +} + +TEST_CASE("Parse logger with sink", "[parse_logger_with_sink]") { + auto sink = spdlog_setup::details::setup_sink(generate_stdout_sink_st()); + const auto sinks_map = + std::unordered_map>{ + {TEST_SINK_NAME, std::move(sink)}}; + + const auto logger = spdlog_setup::details::setup_logger( + generate_simple_logger_with_sink_table(), + sinks_map, + EMPTY_THREAD_POOLS_MAP); + + REQUIRE(logger->name() == TEST_LOGGER_NAME); + REQUIRE(logger->sinks()[0] == sinks_map.at(TEST_SINK_NAME)); +} + +TEST_CASE( + "Parse logger with missing sink", "[parse_logger_with_missing_sink]") { + auto sink = spdlog_setup::details::setup_sink(generate_stdout_sink_st()); + const auto sinks_map = + std::unordered_map>{ + {"xxx", std::move(sink)}}; + + REQUIRE_THROWS_AS( + spdlog_setup::details::setup_logger( + generate_simple_logger_with_sink_table(), + sinks_map, + EMPTY_THREAD_POOLS_MAP), + spdlog_setup::setup_error); +} diff --git a/src/unit_test/loggers.h b/src/unit_test/loggers.h new file mode 100644 index 00000000..630a75aa --- /dev/null +++ b/src/unit_test/loggers.h @@ -0,0 +1,120 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + +#pragma once + +#include "conf.h" + +#include +#include +#include + +static constexpr auto TEST_LOGGER_NAME = "default"; +static constexpr auto TEST_SINK_NAME = "default"; + +inline auto generate_simple_default_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_simple_sync_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::TYPE, names::SYNC); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_simple_async_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::TYPE, names::ASYNC); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_global_async_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::TYPE, names::ASYNC); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_invalid_sync_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::TYPE, "xxx"); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_async_overflow_policy_block_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::TYPE, names::ASYNC); + logger_table->insert(names::OVERFLOW_POLICY, names::BLOCK); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_async_overflow_policy_overrun_oldest_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::TYPE, names::ASYNC); + logger_table->insert(names::OVERFLOW_POLICY, names::OVERRUN_OLDEST); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_invalid_async_overflow_policy_logger_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + logger_table->insert(names::TYPE, names::ASYNC); + logger_table->insert(names::OVERFLOW_POLICY, "xxx"); + logger_table->insert(names::SINKS, cpptoml::make_array()); + return std::move(logger_table); +} + +inline auto generate_simple_logger_with_sink_table() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto logger_table = cpptoml::make_table(); + logger_table->insert(names::NAME, std::string(TEST_LOGGER_NAME)); + + auto sinks = cpptoml::make_array(); + sinks->push_back(std::string(TEST_SINK_NAME)); + logger_table->insert(names::SINKS, std::move(sinks)); + + return std::move(logger_table); +} diff --git a/src/unit_test/unit_test.cpp b/src/unit_test/others.cpp similarity index 99% rename from src/unit_test/unit_test.cpp rename to src/unit_test/others.cpp index 15336dee..a9c53984 100644 --- a/src/unit_test/unit_test.cpp +++ b/src/unit_test/others.cpp @@ -16,10 +16,6 @@ #include "examples.h" #include "spdlog_setup/conf.h" -#include "spdlog_setup/details/third_party/cpptoml.h" -#if defined(SPDLOG_SETUP_CPPTOML_EXTERNAL) -#include "cpptoml.h" -#endif #include #include diff --git a/src/unit_test/sinks.cpp b/src/unit_test/sinks.cpp new file mode 100644 index 00000000..eb032a9c --- /dev/null +++ b/src/unit_test/sinks.cpp @@ -0,0 +1,17 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + +#include "catch.hpp" + +#include "sinks.h" + +#include + +TEST_CASE("Parse stdout sink st", "[parse_generate_stdout_sink_st]") { + const auto sink = + spdlog_setup::details::setup_sink(generate_stdout_sink_st()); + REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stdout_sink_st &)); +} diff --git a/src/unit_test/sinks.h b/src/unit_test/sinks.h new file mode 100644 index 00000000..3f2af162 --- /dev/null +++ b/src/unit_test/sinks.h @@ -0,0 +1,22 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + +#pragma once + +#include "conf.h" + +#include +#include +#include +#include + +inline auto generate_stdout_sink_st() -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto sink_table = cpptoml::make_table(); + sink_table->insert(names::TYPE, std::string("stdout_sink_st")); + return std::move(sink_table); +} diff --git a/src/unit_test/thread_pool.cpp b/src/unit_test/thread_pool.cpp new file mode 100644 index 00000000..23126fb3 --- /dev/null +++ b/src/unit_test/thread_pool.cpp @@ -0,0 +1,64 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + +#include "catch.hpp" + +#include "thread_pool.h" + +TEST_CASE("Parse global thread pool", "[parse_global_thread_pool]") { + // Cannot test queue size and thread count as they are not exposed publicly + // Can only test that the thread pool was changed to another instance + const auto original = spdlog::thread_pool(); + + const auto thread_pools = spdlog_setup::details::setup_thread_pools( + generate_global_thread_pool()); + const auto modified = spdlog::thread_pool(); + + REQUIRE(original != modified); + REQUIRE(thread_pools.empty()); +} + +TEST_CASE("Parse simple thread pool", "[parse_simple_thread_pool]") { + // No way to test non-global thread pool from spdlog sadly + const auto thread_pools = spdlog_setup::details::setup_thread_pools( + generate_simple_thread_pool()); + + REQUIRE(thread_pools.size() == 1); +} + +TEST_CASE("Parse multiple thread pools", "[parse_multiple_thread_pools]") { + const auto thread_pools = spdlog_setup::details::setup_thread_pools( + generate_multiple_thread_pools(10)); + + REQUIRE(thread_pools.size() == 10); +} + +TEST_CASE( + "Parse invalid no name thread pool", + "[parse_invalid_no_name_thread_pool]") { + REQUIRE_THROWS_AS( + spdlog_setup::details::setup_thread_pools( + generate_invalid_no_name_thread_pool()), + spdlog_setup::setup_error); +} + +TEST_CASE( + "Parse invalid no queue size thread pool", + "[parse_invalid_no_queue_size_thread_pool]") { + REQUIRE_THROWS_AS( + spdlog_setup::details::setup_thread_pools( + generate_invalid_no_queue_size_thread_pool()), + spdlog_setup::setup_error); +} + +TEST_CASE( + "Parse invalid no num threads thread pool", + "[parse_invalid_no_num_threads_thread_pool]") { + REQUIRE_THROWS_AS( + spdlog_setup::details::setup_thread_pools( + generate_invalid_no_num_threads_thread_pool()), + spdlog_setup::setup_error); +} diff --git a/src/unit_test/thread_pool.h b/src/unit_test/thread_pool.h new file mode 100644 index 00000000..e9e8a047 --- /dev/null +++ b/src/unit_test/thread_pool.h @@ -0,0 +1,119 @@ +/** + * Unit tests implementation. + * @author Chen Weiguang + * @version 0.3.0-pre + */ + +#pragma once + +#include "conf.h" + +#include +#include +#include + +static constexpr auto TEST_THREAD_POOL_NAME = "default"; + +inline auto generate_global_thread_pool() -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto global_thread_inner_pool_table = cpptoml::make_table(); + global_thread_inner_pool_table->insert(names::QUEUE_SIZE, size_t(1234)); + global_thread_inner_pool_table->insert(names::NUM_THREADS, size_t(9)); + + auto conf = cpptoml::make_table(); + conf->insert( + names::GLOBAL_THREAD_POOL_TABLE, + std::move(global_thread_inner_pool_table)); + + return std::move(conf); +} + +inline auto generate_simple_thread_pool() -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto thread_pool_table_array = cpptoml::make_table_array(); + + auto thread_pool_table = cpptoml::make_table(); + thread_pool_table->insert(names::NAME, TEST_THREAD_POOL_NAME); + thread_pool_table->insert(names::QUEUE_SIZE, size_t(1234)); + thread_pool_table->insert(names::NUM_THREADS, size_t(9)); + thread_pool_table_array->push_back(std::move(thread_pool_table)); + + auto conf = cpptoml::make_table(); + conf->insert(names::THREAD_POOL_TABLE, std::move(thread_pool_table_array)); + + return std::move(conf); +} + +inline auto generate_multiple_thread_pools(const size_t count) + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto thread_pool_table_array = cpptoml::make_table_array(); + + for (size_t i = 0; i < count; ++i) { + auto thread_pool_table = cpptoml::make_table(); + thread_pool_table->insert( + names::NAME, TEST_THREAD_POOL_NAME + std::to_string(i)); + thread_pool_table->insert(names::QUEUE_SIZE, size_t(1234)); + thread_pool_table->insert(names::NUM_THREADS, size_t(9)); + thread_pool_table_array->push_back(std::move(thread_pool_table)); + } + + auto conf = cpptoml::make_table(); + conf->insert(names::THREAD_POOL_TABLE, std::move(thread_pool_table_array)); + + return std::move(conf); +} + +inline auto generate_invalid_no_name_thread_pool() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto thread_pool_table_array = cpptoml::make_table_array(); + + auto thread_pool_table = cpptoml::make_table(); + thread_pool_table->insert(names::QUEUE_SIZE, size_t(1234)); + thread_pool_table->insert(names::NUM_THREADS, size_t(9)); + thread_pool_table_array->push_back(std::move(thread_pool_table)); + + auto conf = cpptoml::make_table(); + conf->insert(names::THREAD_POOL_TABLE, std::move(thread_pool_table_array)); + + return std::move(conf); +} + +inline auto generate_invalid_no_queue_size_thread_pool() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto thread_pool_table_array = cpptoml::make_table_array(); + + auto thread_pool_table = cpptoml::make_table(); + thread_pool_table->insert(names::NAME, TEST_THREAD_POOL_NAME); + thread_pool_table->insert(names::NUM_THREADS, size_t(9)); + thread_pool_table_array->push_back(std::move(thread_pool_table)); + + auto conf = cpptoml::make_table(); + conf->insert(names::THREAD_POOL_TABLE, std::move(thread_pool_table_array)); + + return std::move(conf); +} + +inline auto generate_invalid_no_num_threads_thread_pool() + -> std::shared_ptr { + namespace names = spdlog_setup::details::names; + + auto thread_pool_table_array = cpptoml::make_table_array(); + + auto thread_pool_table = cpptoml::make_table(); + thread_pool_table->insert(names::NAME, TEST_THREAD_POOL_NAME); + thread_pool_table->insert(names::QUEUE_SIZE, size_t(1234)); + thread_pool_table_array->push_back(std::move(thread_pool_table)); + + auto conf = cpptoml::make_table(); + conf->insert(names::THREAD_POOL_TABLE, std::move(thread_pool_table_array)); + + return std::move(conf); +}