diff --git a/nano/lib/logging.cpp b/nano/lib/logging.cpp index 68c2ccc27a..f030b25e8a 100644 --- a/nano/lib/logging.cpp +++ b/nano/lib/logging.cpp @@ -349,7 +349,7 @@ void nano::log_config::deserialize (nano::tomlconfig & toml) if (toml.has_key ("default_level")) { auto default_level_l = toml.get ("default_level"); - default_level = nano::log::to_level (default_level_l); + default_level = nano::log::parse_level (default_level_l); } if (toml.has_key ("console")) @@ -376,7 +376,7 @@ void nano::log_config::deserialize (nano::tomlconfig & toml) try { auto & [name_str, level_str] = level; - auto logger_level = nano::log::to_level (level_str); + auto logger_level = nano::log::parse_level (level_str); auto logger_id = parse_logger_id (name_str); levels[logger_id] = logger_level; @@ -399,14 +399,14 @@ nano::log_config::logger_id_t nano::log_config::parse_logger_id (const std::stri auto pos = logger_name.find ("::"); if (pos == std::string::npos) { - return { nano::log::to_type (logger_name), nano::log::detail::all }; + return { nano::log::parse_type (logger_name), nano::log::detail::all }; } else { 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) }; + return { nano::log::parse_type (logger_type), nano::log::parse_detail (logger_detail) }; } } @@ -430,6 +430,58 @@ nano::log_config nano::load_log_config (nano::log_config fallback, const std::fi try { auto config = nano::load_config_file (fallback, config_filename, data_path, config_overrides); + + // Parse default log level from environment variable, e.g. "NANO_LOG=debug" + auto env_level = nano::get_env ("NANO_LOG"); + if (env_level) + { + try + { + config.default_level = nano::log::parse_level (*env_level); + } + catch (std::invalid_argument const & ex) + { + std::cerr << "Invalid log level from NANO_LOG environment variable: " << ex.what () << std::endl; + } + } + + // Parse per logger levels from environment variable, e.g. "NANO_LOG_LEVELS=ledger=debug,node=trace" + auto env_levels = nano::get_env ("NANO_LOG_LEVELS"); + if (env_levels) + { + std::map levels; + for (auto const & env_level_str : nano::util::split (*env_levels, ',')) + { + try + { + // Split 'logger_name=level' into a pair of 'logger_name' and 'level' + auto arr = nano::util::split (env_level_str, '='); + if (arr.size () != 2) + { + throw std::invalid_argument ("Invalid entry: " + env_level_str); + } + + auto name_str = arr[0]; + auto level_str = arr[1]; + + auto logger_id = nano::log_config::parse_logger_id (name_str); + auto logger_level = nano::log::parse_level (level_str); + + levels[logger_id] = logger_level; + } + catch (std::invalid_argument const & ex) + { + std::cerr << "Invalid log level from NANO_LOG_LEVELS environment variable: " << ex.what () << std::endl; + } + } + + // Merge with existing levels + for (auto const & [logger_id, level] : levels) + { + config.levels[logger_id] = level; + } + } + return config; } catch (std::runtime_error const & ex) diff --git a/nano/lib/logging.hpp b/nano/lib/logging.hpp index a9b10c6693..b00a27d71d 100644 --- a/nano/lib/logging.hpp +++ b/nano/lib/logging.hpp @@ -51,9 +51,9 @@ class log_config final static log_config daemon_default (); static log_config tests_default (); -private: - logger_id_t parse_logger_id (std::string const &); + static logger_id_t parse_logger_id (std::string const &); +private: /// Returns placeholder log levels for all loggers static std::map default_levels (nano::log::level); }; diff --git a/nano/lib/logging_enums.cpp b/nano/lib/logging_enums.cpp index a82996aa70..b1a55f4485 100644 --- a/nano/lib/logging_enums.cpp +++ b/nano/lib/logging_enums.cpp @@ -47,7 +47,7 @@ const std::vector & nano::log::all_types () return all; } -nano::log::level nano::log::to_level (std::string_view name) +nano::log::level nano::log::parse_level (std::string_view name) { auto value = magic_enum::enum_cast (name); if (value.has_value ()) @@ -64,7 +64,7 @@ nano::log::level nano::log::to_level (std::string_view name) } } -nano::log::type nano::log::to_type (std::string_view name) +nano::log::type nano::log::parse_type (std::string_view name) { auto value = magic_enum::enum_cast (name); if (value.has_value ()) @@ -77,7 +77,7 @@ nano::log::type nano::log::to_type (std::string_view name) } } -nano::log::detail nano::log::to_detail (std::string_view name) +nano::log::detail nano::log::parse_detail (std::string_view name) { auto value = magic_enum::enum_cast (name); if (value.has_value ()) diff --git a/nano/lib/logging_enums.hpp b/nano/lib/logging_enums.hpp index 0440307823..508364dd47 100644 --- a/nano/lib/logging_enums.hpp +++ b/nano/lib/logging_enums.hpp @@ -128,13 +128,13 @@ 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); +nano::log::level parse_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); +nano::log::type parse_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); +nano::log::detail parse_detail (std::string_view); std::vector const & all_levels (); std::vector const & all_types (); diff --git a/nano/lib/utility.hpp b/nano/lib/utility.hpp index 5ec06f6e9e..331c0534ab 100644 --- a/nano/lib/utility.hpp +++ b/nano/lib/utility.hpp @@ -245,6 +245,18 @@ std::string join (Container const & container, std::string_view delimiter, Func return join (container.begin (), container.end (), delimiter, transform); } +inline std::vector split (const std::string & str, char delimiter) +{ + std::stringstream ss{ str }; + std::vector result; + std::string item; + while (std::getline (ss, item, delimiter)) + { + result.push_back (item); + } + return result; +} + template std::string to_str (T const & val) {