diff --git a/nano/lib/logging.cpp b/nano/lib/logging.cpp index 333cd01290..d985167a21 100644 --- a/nano/lib/logging.cpp +++ b/nano/lib/logging.cpp @@ -43,7 +43,9 @@ void nano::release_logging () * nlogger */ -nano::nlogger::nlogger (const nano::log_config & config, std::string identifier) +nano::nlogger::nlogger (nano::log_config config, std::string identifier) : + config{ std::move (config) }, + identifier{ std::move (identifier) } { debug_assert (logging_initialized, "initialize_logging must be called before creating a logger"); @@ -124,7 +126,18 @@ spdlog::logger & nano::nlogger::get_logger (nano::log::type tag) std::shared_ptr nano::nlogger::make_logger (nano::log::type tag) { auto spd_logger = std::make_shared (std::string{ to_string (tag) }, sinks.begin (), sinks.end ()); + spdlog::initialize_logger (spd_logger); + + if (auto it = config.levels.find ({ tag, nano::log::detail::all }); it != config.levels.end ()) + { + spd_logger->set_level (to_spdlog_level (it->second)); + } + else + { + spd_logger->set_level (to_spdlog_level (config.default_level)); + } + return spd_logger; } @@ -182,7 +195,7 @@ nano::log_config nano::log_config::tests_default () nano::error nano::log_config::serialize (nano::tomlconfig & toml) const { - toml.put ("level", std::string{ to_string (default_level) }); + toml.put ("default_level", std::string{ to_string (default_level) }); nano::tomlconfig console_config; console_config.put ("enable", console.enable); @@ -196,17 +209,26 @@ nano::error nano::log_config::serialize (nano::tomlconfig & toml) const file_config.put ("rotation_count", file.rotation_count); toml.put_child ("file", file_config); + nano::tomlconfig levels_config; + for (auto const & [logger_id, level] : levels) + { + auto logger_name = to_string (logger_id.first); + levels_config.put (std::string{ logger_name }, std::string{ to_string (level) }); + } + toml.put_child ("levels", levels_config); + return toml.get_error (); } +// TODO: Move handling of deserialization exceptions outside of this function nano::error nano::log_config::deserialize (nano::tomlconfig & toml) { try { - if (toml.has_key ("level")) + if (toml.has_key ("default_level")) { - auto default_level_l = toml.get ("level"); - default_level = parse_level (default_level_l); + auto default_level_l = toml.get ("default_level"); + default_level = nano::log::to_level (default_level_l); } if (toml.has_key ("console")) @@ -224,9 +246,29 @@ nano::error nano::log_config::deserialize (nano::tomlconfig & toml) file.max_size = file_config.get ("max_size"); file.rotation_count = file_config.get ("rotation_count"); } - } - catch (std::runtime_error const & ex) + if (toml.has_key ("levels")) + { + auto levels_config = toml.get_required_child ("levels"); + for (auto & level : levels_config.get_values ()) + { + try + { + auto & [name_str, level_str] = level; + auto logger_level = nano::log::to_level (level_str); + auto logger_id = parse_logger_id (name_str); + + levels[logger_id] = logger_level; + } + catch (std::invalid_argument const & ex) + { + // Ignore but warn about invalid logger names + nano::log::warn ("Processing log config: {}", ex.what ()); + } + } + } + } + catch (std::invalid_argument const & ex) { toml.get_error ().set (ex.what ()); } @@ -234,40 +276,34 @@ nano::error nano::log_config::deserialize (nano::tomlconfig & toml) return toml.get_error (); } -nano::log::level nano::log_config::parse_level (const std::string & level) +/** + * Parse `logger_name[:logger_detail]` into a pair of `log::type` and `log::detail` + * @throw std::invalid_argument if `logger_name` or `logger_detail` are invalid + */ +nano::log_config::logger_id_t nano::log_config::parse_logger_id (const std::string & logger_name) { - if (level == "off") - { - return nano::log::level::off; - } - else if (level == "critical") - { - return nano::log::level::critical; - } - else if (level == "error") - { - return nano::log::level::error; - } - else if (level == "warn") - { - return nano::log::level::warn; - } - else if (level == "info") + auto pos = logger_name.find (":"); + if (pos == std::string::npos) { - return nano::log::level::info; + return { nano::log::to_type (logger_name), nano::log::detail::all }; } - else if (level == "debug") - { - return nano::log::level::debug; - } - else if (level == "trace") + else { - return nano::log::level::trace; + auto logger_type = logger_name.substr (0, pos); + auto logger_detail = logger_name.substr (pos + 1); + + return { nano::log::to_type (logger_type), nano::log::to_detail (logger_detail) }; } - else +} + +std::map nano::log_config::default_levels (nano::log::level default_level) +{ + std::map result; + for (auto const & type : nano::log::all_types ()) { - throw std::runtime_error ("Invalid log level: " + level + ". Must be one of: off, critical, error, warn, info, debug, trace"); + result.emplace (std::make_pair (type, nano::log::detail::all), default_level); } + return result; } nano::error nano::read_log_config_toml (const std::filesystem::path & data_path, nano::log_config & config, const std::vector & config_overrides) diff --git a/nano/lib/logging.hpp b/nano/lib/logging.hpp index cdb35259b5..91651fc9bf 100644 --- a/nano/lib/logging.hpp +++ b/nano/lib/logging.hpp @@ -18,12 +18,12 @@ class log_config final nano::error serialize (nano::tomlconfig &) const; nano::error deserialize (nano::tomlconfig &); -private: - nano::log::level parse_level (std::string const &); - public: nano::log::level default_level{ nano::log::level::info }; + using logger_id_t = std::pair; + std::map levels{ default_levels (nano::log::level::info) }; + struct console_config { bool enable{ true }; @@ -41,12 +41,16 @@ class log_config final console_config console; file_config file; - // TODO: Per logger type levels - public: // Predefined defaults static log_config cli_default (); static log_config daemon_default (); static log_config tests_default (); + +private: + logger_id_t parse_logger_id (std::string const &); + + /// Returns placeholder log levels for all loggers + static std::map default_levels (nano::log::level); }; nano::error read_log_config_toml (std::filesystem::path const & data_path, nano::log_config & config, std::vector const & overrides); @@ -62,7 +66,7 @@ spdlog::level::level_enum to_spdlog_level (nano::log::level); class nlogger final { public: - nlogger (nano::log_config const &, std::string identifier = ""); + nlogger (nano::log_config, std::string identifier = ""); // Disallow copies nlogger (nlogger const &) = delete; @@ -105,6 +109,9 @@ class nlogger final } private: + const nano::log_config config; + const std::string identifier; + std::vector sinks; std::unordered_map> spd_loggers; std::shared_mutex mutex; diff --git a/nano/lib/logging_enums.cpp b/nano/lib/logging_enums.cpp index 8d83d33833..a82996aa70 100644 --- a/nano/lib/logging_enums.cpp +++ b/nano/lib/logging_enums.cpp @@ -6,17 +6,86 @@ #include -std::string_view nano::to_string (nano::log::type tag) +std::string_view nano::log::to_string (nano::log::type tag) { return magic_enum::enum_name (tag); } -std::string_view nano::to_string (nano::log::detail detail) +std::string_view nano::log::to_string (nano::log::detail detail) { return magic_enum::enum_name (detail); } -std::string_view nano::to_string (nano::log::level level) +std::string_view nano::log::to_string (nano::log::level level) { return magic_enum::enum_name (level); +} + +const std::vector & nano::log::all_levels () +{ + static std::vector all = [] () { + std::vector result; + for (auto const & lvl : magic_enum::enum_values ()) + { + result.push_back (lvl); + } + return result; + }(); + return all; +} + +const std::vector & nano::log::all_types () +{ + static std::vector all = [] () { + std::vector result; + for (auto const & lvl : magic_enum::enum_values ()) + { + result.push_back (lvl); + } + return result; + }(); + return all; +} + +nano::log::level nano::log::to_level (std::string_view name) +{ + auto value = magic_enum::enum_cast (name); + if (value.has_value ()) + { + return value.value (); + } + else + { + auto all_levels_str = nano::util::join (nano::log::all_levels (), ", ", [] (auto const & lvl) { + return to_string (lvl); + }); + + throw std::invalid_argument ("Invalid log level: " + std::string (name) + ". Must be one of: " + all_levels_str); + } +} + +nano::log::type nano::log::to_type (std::string_view name) +{ + auto value = magic_enum::enum_cast (name); + if (value.has_value ()) + { + return value.value (); + } + else + { + throw std::invalid_argument ("Invalid log type: " + std::string (name)); + } +} + +nano::log::detail nano::log::to_detail (std::string_view name) +{ + auto value = magic_enum::enum_cast (name); + if (value.has_value ()) + { + return value.value (); + } + else + { + throw std::invalid_argument ("Invalid log detail: " + std::string (name)); + } } \ No newline at end of file diff --git a/nano/lib/logging_enums.hpp b/nano/lib/logging_enums.hpp index 1ba108605e..8476d18797 100644 --- a/nano/lib/logging_enums.hpp +++ b/nano/lib/logging_enums.hpp @@ -69,13 +69,6 @@ enum class type bootstrap_legacy, }; -enum class category -{ - all = 0, // reserved - - work_generation, -}; - enum class detail { all = 0, // reserved @@ -112,17 +105,31 @@ enum class detail }; -enum class preset +// TODO: Additionally categorize logs by categories which can be enabled/disabled independently +enum class category { - cli, - daemon, - tests, + all = 0, // reserved + + work_generation, + // ... }; } -namespace nano +namespace nano::log { std::string_view to_string (nano::log::type); std::string_view to_string (nano::log::detail); std::string_view to_string (nano::log::level); + +/// @throw std::invalid_argument if the input string does not match a log::level +nano::log::level to_level (std::string_view); + +/// @throw std::invalid_argument if the input string does not match a log::type +nano::log::type to_type (std::string_view); + +/// @throw std::invalid_argument if the input string does not match a log::detail +nano::log::detail to_detail (std::string_view); + +std::vector const & all_levels (); +std::vector const & all_types (); } diff --git a/nano/lib/tomlconfig.hpp b/nano/lib/tomlconfig.hpp index e04af3bc05..140e1b0ab9 100644 --- a/nano/lib/tomlconfig.hpp +++ b/nano/lib/tomlconfig.hpp @@ -171,6 +171,19 @@ class tomlconfig : public nano::configbase return *this; } + template + std::vector> get_values () + { + std::vector> result; + for (auto & entry : *tree) + { + T target{}; + get_config (true, entry.first, target, target); + result.push_back ({ entry.first, target }); + } + return result; + } + protected: template ::value>> tomlconfig & get_config (bool optional, std::string const & key, T & target, T default_value = T ())