From dc8b1ccb1028b8bada003352f28917a04f1d1184 Mon Sep 17 00:00:00 2001 From: Simon Yeung Date: Tue, 19 Nov 2024 10:02:34 -0800 Subject: [PATCH 1/6] Use log channel to divide traffic for various plugin logging and clean up legacy code --- conanfile.py | 2 +- src/mtconnect/configuration/agent_config.cpp | 152 ++++++++++++------- src/mtconnect/configuration/agent_config.hpp | 55 ++++--- src/mtconnect/logging.hpp | 31 ++-- 4 files changed, 148 insertions(+), 92 deletions(-) diff --git a/conanfile.py b/conanfile.py index 745600d7..6da2500a 100644 --- a/conanfile.py +++ b/conanfile.py @@ -9,7 +9,7 @@ class MTConnectAgentConan(ConanFile): name = "mtconnect_agent" - version = "2.3" + version = "2.4" url = "https://github.com/mtconnect/cppagent.git" license = "Apache License 2.0" settings = "os", "compiler", "arch", "build_type" diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 14616809..52909b73 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -101,8 +101,6 @@ BOOST_LOG_ATTRIBUTE_KEYWORD(named_scope, "Scope", logr::attributes::named_scope: BOOST_LOG_ATTRIBUTE_KEYWORD(utc_timestamp, "Timestamp", logr::attributes::utc_clock::value_type); namespace mtconnect::configuration { - AGENT_LIB_API - boost::log::trivial::logger_type *gAgentLogger = nullptr; AgentConfiguration::AgentConfiguration() : m_context {make_unique()}, m_monitorTimer(m_context->get()) @@ -235,8 +233,10 @@ namespace mtconnect::configuration { #endif m_context.reset(); - if (m_sink) - m_sink.reset(); + for (auto &[channelName, logChannel] : m_logChannels) + logChannel.m_logSink.reset(); + + m_logChannels.clear(); logr::core::get()->remove_all_sinks(); } @@ -433,8 +433,10 @@ namespace mtconnect::configuration { void AgentConfiguration::setLoggingLevel(const logr::trivial::severity_level level) { using namespace logr::trivial; - m_logLevel = level; - logr::core::get()->set_filter(severity >= level); + for ( auto &[channelName, logChannel] : m_logChannels) + { + logChannel.m_logLevel = level; + } } static logr::trivial::severity_level StringToLogLevel(const std::string &level) @@ -476,11 +478,9 @@ namespace mtconnect::configuration { void AgentConfiguration::configureLogger(const ptree &config) { using namespace logr::trivial; - namespace kw = boost::log::keywords; namespace expr = logr::expressions; logr::core::get()->remove_all_sinks(); - m_sink.reset(); //// Add the commonly used attributes; includes TimeStamp, ProcessID and ThreadID and others logr::add_common_attributes(); @@ -489,12 +489,43 @@ namespace mtconnect::configuration { logr::attributes::current_thread_id()); logr::core::get()->add_global_attribute("Timestamp", logr::attributes::utc_clock()); + m_logger = &::boost::log::trivial::logger::get(); + + setLoggingLevel(severity_level::info); + + auto formatter = + expr::stream << expr::format_date_time("Timestamp", + "%Y-%m-%dT%H:%M:%S.%fZ ") + << "(" + << expr::attr("ThreadID") + << ") [" << severity << "] " << named_scope << ": " << expr::smessage; + + configureLoggerChannel("agent", config, formatter); + } + + void AgentConfiguration::configureLoggerChannel(const std::string &channelName, const ptree &config, std::optional> formatter) + { + using namespace logr::trivial; + namespace expr = logr::expressions; + namespace kw = boost::log::keywords; + + auto &logChannel = m_logChannels[channelName]; + if (logChannel.m_channelName == "") + logChannel.m_channelName = channelName; + + if (!formatter) + { + formatter = + expr::stream << expr::format_date_time("Timestamp", + "%Y-%m-%dT%H:%M:%S.%fZ ") + << std::setw(7) << std::left << boost::log::trivial::severity << " " << expr::message; + } + ptree empty; auto logger = config.get_child_optional("logger_config").value_or(empty); - setLoggingLevel(severity_level::info); - static const string defaultFileName {"agent.log"}; - static const string defaultArchivePattern("agent_%Y-%m-%d_%H-%M-%S_%N.log"); + const string defaultFileName = channelName + ".log"; + const string defaultArchivePattern = channelName + "_%Y-%m-%d_%H-%M-%S_%N.log"; ConfigOptions options; AddDefaultedOptions(logger, options, @@ -507,19 +538,10 @@ namespace mtconnect::configuration { {{"output", string()}, {"level", string()}, {"logging_level", string()}}); auto output = GetOption(options, "output"); - auto level = setLoggingLevel( + auto level = StringToLogLevel( GetOption(options, "level") .value_or(GetOption(options, "logging_level").value_or("info"s))); - gAgentLogger = m_logger = &::boost::log::trivial::logger::get(); - - auto formatter = - expr::stream << expr::format_date_time("Timestamp", - "%Y-%m-%dT%H:%M:%S.%fZ ") - << "(" - << expr::attr("ThreadID") - << ") [" << severity << "] " << named_scope << ": " << expr::smessage; - if (m_isDebug || (output && (*output == "cout" || *output == "cerr"))) { ostream *out; @@ -528,11 +550,21 @@ namespace mtconnect::configuration { else out = &std::cout; - logr::add_console_log(*out, kw::format = formatter, kw::auto_flush = true); - if (m_isDebug && level >= severity_level::debug) - setLoggingLevel(severity_level::debug); + level = severity_level::debug; + + auto sink = boost::make_shared(); + logChannel.m_logSink = sink; + logChannel.m_logLevel = level; + logChannel.m_logFileName = output.value(); + + sink->locked_backend()->add_stream(boost::shared_ptr(out, boost::null_deleter())); + sink->locked_backend()->auto_flush(true); + + sink->set_formatter(formatter.value()); + sink->set_filter(expr::attr("Channel") == logChannel.m_channelName && severity >= logChannel.m_logLevel); + logr::core::get()->add_sink(sink); return; } @@ -560,16 +592,23 @@ namespace mtconnect::configuration { } } - m_maxLogFileSize = ConvertFileSize(options, "max_size", m_maxLogFileSize); - m_logRotationSize = ConvertFileSize(options, "rotation_size", m_logRotationSize); + auto &maxLogFileSize = logChannel.m_maxLogFileSize; + auto &logRotationSize = logChannel.m_logRotationSize; + auto &rotationLogInterval = logChannel.m_rotationLogInterval; + auto &logArchivePattern = logChannel.m_logArchivePattern; + auto &logDirectory = logChannel.m_logDirectory; + auto &logFileName = logChannel.m_logFileName; + + maxLogFileSize = ConvertFileSize(options, "max_size", maxLogFileSize); + logRotationSize = ConvertFileSize(options, "rotation_size", logRotationSize); int max_index = *GetOption(options, "max_index"); if (auto sched = GetOption(options, "schedule")) { if (*sched == "DAILY") - m_rotationLogInterval = 24; + rotationLogInterval = 24; else if (*sched == "WEEKLY") - m_rotationLogInterval = 168; + rotationLogInterval = 168; else if (*sched != "NEVER") LOG(error) << "Invalid schedule value."; } @@ -578,52 +617,55 @@ namespace mtconnect::configuration { auto file_name = *GetOption(options, "file_name"); auto archive_pattern = *GetOption(options, "archive_pattern"); - m_logArchivePattern = fs::path(archive_pattern); - if (!m_logArchivePattern.has_filename()) + logArchivePattern = fs::path(archive_pattern); + if (!logArchivePattern.has_filename()) { - m_logArchivePattern = - m_logArchivePattern / archiveFileName(get(options["file_name"])); + logArchivePattern = + logArchivePattern / archiveFileName(get(options["file_name"])); } - if (m_logArchivePattern.is_relative()) - m_logArchivePattern = fs::current_path() / m_logArchivePattern; + if (logArchivePattern.is_relative()) + logArchivePattern = fs::current_path() / logArchivePattern; // Get the log directory from the archive path. - m_logDirectory = m_logArchivePattern.parent_path(); + logDirectory = logArchivePattern.parent_path(); // If the file name does not specify a log directory, use the // archive directory - m_logFileName = fs::path(file_name); - if (!m_logFileName.has_parent_path()) - m_logFileName = m_logDirectory / m_logFileName; - else if (m_logFileName.is_relative()) - m_logFileName = fs::current_path() / m_logFileName; - - boost::shared_ptr core = logr::core::get(); + logFileName = fs::path(file_name); + if (!logFileName.has_parent_path()) + logFileName = logDirectory / logFileName; + else if (logFileName.is_relative()) + logFileName = fs::current_path() / logFileName; // Create a text file sink - m_sink = boost::make_shared( - kw::file_name = m_logFileName, kw::target_file_name = m_logArchivePattern.filename(), - kw::auto_flush = true, kw::rotation_size = m_logRotationSize, + auto sink = boost::make_shared( + kw::file_name = logFileName, kw::target_file_name = logArchivePattern.filename(), + kw::auto_flush = true, kw::rotation_size = logRotationSize, kw::open_mode = ios_base::out | ios_base::app, kw::format = formatter); + logChannel.m_logSink = sink; + logChannel.m_logLevel = level; + // Set up where the rotated files will be stored - m_sink->locked_backend()->set_file_collector(logr::sinks::file::make_collector( - kw::target = m_logDirectory, kw::max_size = m_maxLogFileSize, kw::max_files = max_index)); + sink->locked_backend()->set_file_collector(logr::sinks::file::make_collector( + kw::target = logDirectory, kw::max_size = maxLogFileSize, kw::max_files = max_index)); - if (m_rotationLogInterval > 0) + if (rotationLogInterval > 0) { - m_sink->locked_backend()->set_time_based_rotation( + sink->locked_backend()->set_time_based_rotation( logr::sinks::file::rotation_at_time_interval( - boost::posix_time::hours(m_rotationLogInterval))); + boost::posix_time::hours(rotationLogInterval))); } // Upon restart, scan the target directory for files matching the file_name pattern - m_sink->locked_backend()->scan_for_files(); - m_sink->set_formatter(formatter); + sink->locked_backend()->scan_for_files(); + + sink->set_formatter(formatter.value()); + sink->set_filter(expr::attr("Channel") == logChannel.m_channelName && severity >= logChannel.m_logLevel); // Formatter for the logger - core->add_sink(m_sink); + logr::core::get()->add_sink(sink); } static std::string ExpandValue(const std::map &values, @@ -736,8 +778,8 @@ namespace mtconnect::configuration { cerr << "could not load config file: " << e.what() << endl; throw e; } - // if (!m_loggerFile) - if (!m_sink) + + if (m_logChannels.empty()) { configureLogger(config); } diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 61b39bbd..07090dc9 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -110,8 +110,15 @@ namespace mtconnect { void initialize(const boost::program_options::variables_map &options) override; /// @brief Configure the logger with the config node from the config file + /// @param channelName the log channel name + /// @param config the configuration node + /// @param formatter optional custom message format + void configureLoggerChannel(const std::string &channelName, const ptree &config, std::optional> formatter = std::nullopt); + + /// @brief Configure the agent logger with the config node from the config file /// @param config the configuration node void configureLogger(const ptree &config); + /// @brief load a configuration text /// @param[in] text the configuration text loaded from a file /// @param[in] fmt the file format, can be MTCONNECT, JSON, or XML @@ -148,22 +155,22 @@ namespace mtconnect { ///@{ /// @brief gets the boost log sink /// @return boost log sink - const auto &getLoggerSink() const { return m_sink; } + const auto &getLoggerSink(const std::string &channelName = "agent") { return m_logChannels[channelName].m_logSink; } /// @brief gets the log directory /// @return log directory - const auto &getLogDirectory() const { return m_logDirectory; } + const auto &getLogDirectory(const std::string &channelName = "agent") { return m_logChannels[channelName].m_logDirectory; } /// @brief get the logging file name /// @return log file name - const auto &getLogFileName() const { return m_logFileName; } + const auto &getLogFileName(const std::string &channelName = "agent") { return m_logChannels[channelName].m_logFileName; } /// @brief for log rolling, get the log archive pattern /// @return log archive pattern - const auto &getLogArchivePattern() const { return m_logArchivePattern; } + const auto &getLogArchivePattern(const std::string &channelName = "agent") { return m_logChannels[channelName].m_logArchivePattern; } /// @brief Get the maximum size of all the log files /// @return the maximum size of all log files - auto getMaxLogFileSize() const { return m_maxLogFileSize; } + auto getMaxLogFileSize(const std::string &channelName = "agent") { return m_logChannels[channelName].m_maxLogFileSize; } /// @brief the maximum size of a log file when it triggers rolling over /// @return the maxumum site of a log file - auto getLogRotationSize() const { return m_logRotationSize; } + auto getLogRotationSize(const std::string &channelName = "agent") { return m_logChannels[channelName].m_logRotationSize; } /// @brief How often to roll over the log file /// /// One of: @@ -172,10 +179,10 @@ namespace mtconnect { /// - `NEVER` /// /// @return the log file interval - auto getRotationLogInterval() const { return m_rotationLogInterval; } + auto getRotationLogInterval(const std::string &channelName = "agent") { return m_logChannels[channelName].m_rotationLogInterval; } /// @brief Get the current log level /// @return log level - auto getLogLevel() const { return m_logLevel; } + auto getLogLevel(const std::string &channelName = "agent") { return m_logChannels[channelName].m_logLevel; } /// @brief set the logging level /// @param[in] level the new logging level @@ -308,6 +315,25 @@ namespace mtconnect { protected: using text_sink = boost::log::sinks::synchronous_sink; + using console_sink = boost::log::sinks::synchronous_sink; + + struct LogChannel + { + std::string m_channelName; + std::filesystem::path m_logDirectory; + std::filesystem::path m_logArchivePattern; + std::filesystem::path m_logFileName; + + int64_t m_maxLogFileSize {0}; + int64_t m_logRotationSize {0}; + int64_t m_rotationLogInterval {0}; + + boost::log::trivial::severity_level m_logLevel {boost::log::trivial::severity_level::info}; + + boost::shared_ptr m_logSink; + }; + + std::map m_logChannels; std::map m_initializers; @@ -316,7 +342,7 @@ namespace mtconnect { pipeline::PipelineContextPtr m_pipelineContext; std::unique_ptr m_adapterHandler; - boost::shared_ptr m_sink; + std::string m_version; std::string m_devicesFile; std::filesystem::path m_exePath; @@ -335,17 +361,6 @@ namespace mtconnect { std::optional m_configTime; std::optional m_deviceTime; - // Logging info for testing - std::filesystem::path m_logDirectory; - std::filesystem::path m_logFileName; - std::filesystem::path m_logArchivePattern; - - boost::log::trivial::severity_level m_logLevel {boost::log::trivial::severity_level::info}; - - int64_t m_maxLogFileSize {0}; - int64_t m_logRotationSize {0}; - int64_t m_rotationLogInterval {0}; - // Factories sink::SinkFactory m_sinkFactory; source::SourceFactory m_sourceFactory; diff --git a/src/mtconnect/logging.hpp b/src/mtconnect/logging.hpp index 0d21a757..5171df74 100644 --- a/src/mtconnect/logging.hpp +++ b/src/mtconnect/logging.hpp @@ -22,28 +22,27 @@ #include #include +#include +#include #include "mtconnect/config.hpp" -/// @brief synonym for `BOOST_LOG_TRIVIAL` -#define LOG BOOST_LOG_TRIVIAL +typedef boost::log::sources::severity_channel_logger_mt +< + boost::log::trivial::severity_level, // the type of the severity level + std::string // the type of the channel name +> channel_logger_mt; + +BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT(agent_logger, channel_logger_mt) +{ + // Specify the channel name on construction, similarly as with the channel_logger + return channel_logger_mt(boost::log::keywords::channel = "agent"); +} + +#define LOG(lvl) BOOST_LOG_SEV(agent_logger::get(), ::boost::log::trivial::lvl) /// @brief synonym for `BOOST_LOG_NAMED_SCOPE` #define NAMED_SCOPE BOOST_LOG_NAMED_SCOPE -// Must be initialized in the plugin before callign log as follows: -// mtconnect::gAgentLogger = config->getLogger(); -// After that, use PLUGIN_LOG(lvl) << ...; -namespace mtconnect { - namespace configuration { - AGENT_LIB_API - extern boost::log::trivial::logger_type *gAgentLogger; - } // namespace configuration -} // namespace mtconnect - #define LOG_LEVEL(lvl) ::boost::log::trivial::lvl -/// @brief Used when static using static agent_lib in a plugin shared object -#define PLUGIN_LOG(lvl) \ - BOOST_LOG_STREAM_WITH_PARAMS(*mtconnect::configuration::gAgentLogger, \ - (::boost::log::keywords::severity = ::boost::log::trivial::lvl)) From f87b3bbb5279a219ddb9c4cdcbe30f5ebc8246cc Mon Sep 17 00:00:00 2001 From: Simon Yeung Date: Tue, 19 Nov 2024 12:31:16 -0800 Subject: [PATCH 2/6] update tests --- test_package/testadapter_service.hpp | 2 -- test_package/testsink_service.hpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/test_package/testadapter_service.hpp b/test_package/testadapter_service.hpp index 29ec1c06..f92ab468 100644 --- a/test_package/testadapter_service.hpp +++ b/test_package/testadapter_service.hpp @@ -62,8 +62,6 @@ namespace mtconnect { static void register_factory(const boost::property_tree::ptree &block, configuration::AgentConfiguration &config) { - mtconnect::configuration::gAgentLogger = config.getLogger(); - PLUGIN_LOG(debug) << "Registering adapter factory for adapter_plugin_test"; config.getSourceFactory().registerFactory("adapter_plugin_test", &adapter_plugin_test::create); } diff --git a/test_package/testsink_service.hpp b/test_package/testsink_service.hpp index affd7c3f..7a011f78 100644 --- a/test_package/testsink_service.hpp +++ b/test_package/testsink_service.hpp @@ -51,8 +51,6 @@ namespace mtconnect { static void register_factory(const boost::property_tree::ptree &block, configuration::AgentConfiguration &config) { - mtconnect::configuration::gAgentLogger = config.getLogger(); - PLUGIN_LOG(debug) << "Registering sink factory for sink_plugin_test"; config.getSinkFactory().registerFactory("sink_plugin_test", &sink_plugin_test::create); } }; From b152f4be00724e9e7a0e3f6cbe6fdc3b7c848e5d Mon Sep 17 00:00:00 2001 From: Simon Yeung Date: Tue, 19 Nov 2024 12:45:52 -0800 Subject: [PATCH 3/6] define CHANNEL_LOGGER_INIT --- src/mtconnect/logging.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mtconnect/logging.hpp b/src/mtconnect/logging.hpp index 5171df74..3ac48e51 100644 --- a/src/mtconnect/logging.hpp +++ b/src/mtconnect/logging.hpp @@ -33,11 +33,11 @@ typedef boost::log::sources::severity_channel_logger_mt std::string // the type of the channel name > channel_logger_mt; -BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT(agent_logger, channel_logger_mt) -{ - // Specify the channel name on construction, similarly as with the channel_logger - return channel_logger_mt(boost::log::keywords::channel = "agent"); -} +#define CHANNEL_LOGGER_INIT(logger, channelName) \ +BOOST_LOG_INLINE_GLOBAL_LOGGER_INIT(logger, channel_logger_mt) \ +{ return channel_logger_mt(boost::log::keywords::channel = channelName); } + +CHANNEL_LOGGER_INIT(agent_logger, "agent") #define LOG(lvl) BOOST_LOG_SEV(agent_logger::get(), ::boost::log::trivial::lvl) From b73d2b5f9faf792a0a522b6f64ea735f6e15c2fb Mon Sep 17 00:00:00 2001 From: Simon Yeung Date: Tue, 19 Nov 2024 14:52:15 -0800 Subject: [PATCH 4/6] fix a regression exception --- src/mtconnect/configuration/agent_config.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 52909b73..ee3a7381 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -432,11 +432,8 @@ namespace mtconnect::configuration { void AgentConfiguration::setLoggingLevel(const logr::trivial::severity_level level) { - using namespace logr::trivial; for ( auto &[channelName, logChannel] : m_logChannels) - { logChannel.m_logLevel = level; - } } static logr::trivial::severity_level StringToLogLevel(const std::string &level) @@ -480,19 +477,19 @@ namespace mtconnect::configuration { using namespace logr::trivial; namespace expr = logr::expressions; - logr::core::get()->remove_all_sinks(); + auto core = logr::core::get(); + + core->remove_all_sinks(); //// Add the commonly used attributes; includes TimeStamp, ProcessID and ThreadID and others logr::add_common_attributes(); - logr::core::get()->add_global_attribute("Scope", logr::attributes::named_scope()); - logr::core::get()->add_global_attribute(logr::aux::default_attribute_names::thread_id(), + core->add_global_attribute("Scope", logr::attributes::named_scope()); + core->add_global_attribute(logr::aux::default_attribute_names::thread_id(), logr::attributes::current_thread_id()); - logr::core::get()->add_global_attribute("Timestamp", logr::attributes::utc_clock()); + core->add_global_attribute("Timestamp", logr::attributes::utc_clock()); m_logger = &::boost::log::trivial::logger::get(); - setLoggingLevel(severity_level::info); - auto formatter = expr::stream << expr::format_date_time("Timestamp", "%Y-%m-%dT%H:%M:%S.%fZ ") @@ -556,7 +553,7 @@ namespace mtconnect::configuration { auto sink = boost::make_shared(); logChannel.m_logSink = sink; logChannel.m_logLevel = level; - logChannel.m_logFileName = output.value(); + logChannel.m_logFileName = output.value_or("debug"); sink->locked_backend()->add_stream(boost::shared_ptr(out, boost::null_deleter())); sink->locked_backend()->auto_flush(true); From 7f1e1bb97adc5919f6e42f86335fb297f1fb8f74 Mon Sep 17 00:00:00 2001 From: Simon Yeung Date: Wed, 20 Nov 2024 09:25:33 -0800 Subject: [PATCH 5/6] update version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1420880c..94ee8c9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 4) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 5) +set(AGENT_VERSION_BUILD 6) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent From 103949c1cbb36f4397eeceb16a3ad1b93d4b6be2 Mon Sep 17 00:00:00 2001 From: Simon Yeung Date: Wed, 20 Nov 2024 09:31:36 -0800 Subject: [PATCH 6/6] remove getLogger() --- src/mtconnect/configuration/agent_config.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 07090dc9..5cdb0a56 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -192,10 +192,6 @@ namespace mtconnect { /// @return the logging level boost::log::trivial::severity_level setLoggingLevel(const std::string &level); - /// @brief get a pointer to the logger - boost::log::trivial::logger_type *getLogger() { return m_logger; } - ///@} - std::optional findConfigFile(const std::string &file) { return findFile(m_configPaths, file);