diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc index 4b03b8ceab..0cb5e94c96 100644 --- a/src/lib/log/logger_manager_impl.cc +++ b/src/lib/log/logger_manager_impl.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -62,8 +62,10 @@ LoggerManagerImpl::processEnd() { // add output specifications. void LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) { - log4cplus::Logger logger = log4cplus::Logger::getInstance( - expandLoggerName(spec.getName())); + string const& name(spec.getName()); + string const& root_logger_name(getRootLoggerName()); + + log4cplus::Logger logger = log4cplus::Logger::getInstance(expandLoggerName(name)); // Set severity level according to specification entry. logger.setLogLevel(LoggerLevelImpl::convertFromBindLevel( @@ -72,36 +74,48 @@ LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) { // Set the additive flag. logger.setAdditivity(spec.getAdditive()); + // Replace all appenders for this logger. + logger.removeAllAppenders(); + + if (name == root_logger_name) { + // Store a copy of the root specification. It might be required later. + root_spec_ = spec; + } + // Output options given? if (spec.optionCount() > 0) { - // Replace all appenders for this logger. - logger.removeAllAppenders(); + // If there are output options provided, continue with the given spec. + appenderFactory(logger, spec); + } else { + // If there are no output options, inherit them from the root logger. + appenderFactory(logger, root_spec_); + } +} - // Now process output specifications. - for (LoggerSpecification::const_iterator i = spec.begin(); - i != spec.end(); ++i) { - switch (i->destination) { - case OutputOption::DEST_CONSOLE: - createConsoleAppender(logger, *i); - break; +void +LoggerManagerImpl::appenderFactory(log4cplus::Logger& logger, + LoggerSpecification const& spec) { + for (OutputOption const& i : spec) { + switch (i.destination) { + case OutputOption::DEST_CONSOLE: + createConsoleAppender(logger, i); + break; - case OutputOption::DEST_FILE: - createFileAppender(logger, *i); - break; + case OutputOption::DEST_FILE: + createFileAppender(logger, i); + break; - case OutputOption::DEST_SYSLOG: - createSyslogAppender(logger, *i); - break; + case OutputOption::DEST_SYSLOG: + createSyslogAppender(logger, i); + break; - default: - // Not a valid destination. As we are in the middle of updating - // logging destinations, we could be in the situation where - // there are no valid appenders. For this reason, throw an - // exception. - isc_throw(UnknownLoggingDestination, - "Unknown logging destination, code = " << - i->destination); - } + default: + // Not a valid destination. As we are in the middle of updating + // logging destinations, we could be in the situation where + // there are no valid appenders. For this reason, throw an + // exception. + isc_throw(UnknownLoggingDestination, + "Unknown logging destination, code = " << i.destination); } } } diff --git a/src/lib/log/logger_manager_impl.h b/src/lib/log/logger_manager_impl.h index 7f1ddb5734..c459cfe646 100644 --- a/src/lib/log/logger_manager_impl.h +++ b/src/lib/log/logger_manager_impl.h @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2019 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -11,6 +11,7 @@ #include #include +#include // Forward declaration to avoid need to include log4cplus header file here. namespace log4cplus { @@ -22,7 +23,6 @@ namespace isc { namespace log { // Forward declarations -class LoggerSpecification; struct OutputOption; /// \brief Logger Manager Implementation @@ -57,7 +57,7 @@ public: /// Processes the specification for a single logger. /// /// \param spec Logging specification for this logger - static void processSpecification(const LoggerSpecification& spec); + void processSpecification(const LoggerSpecification& spec); /// \brief End Processing /// @@ -94,6 +94,16 @@ public: int dbglevel = 0); private: + /// @brief Decides what appender to create. + /// + /// Delegates to the other functions that create appenders based on what's + /// in spec. + /// + /// @param logger log4cplus logger to which the appender must be attached + /// @param spec the configured specification consisting of output options + static void appenderFactory(log4cplus::Logger& logger, + LoggerSpecification const& spec); + /// \brief Create console appender /// /// Creates an object that, when attached to a logger, will log to one @@ -171,10 +181,14 @@ private: /// \c storeBufferAppenders(), and clears it void flushBufferAppenders(); - /// Only used between processInit() and processEnd(), to temporarily + /// @brief Only used between processInit() and processEnd(), to temporarily /// store the buffer appenders in order to flush them after /// processSpecification() calls have been completed std::vector buffer_appender_store_; + + /// @brief A hard copy of the specification for the root logger used for + /// inheritance by child loggers. + LoggerSpecification root_spec_; }; } // namespace log diff --git a/src/lib/log/tests/logger_manager_unittest.cc b/src/lib/log/tests/logger_manager_unittest.cc index 3a5b76fa16..6bde1db12b 100644 --- a/src/lib/log/tests/logger_manager_unittest.cc +++ b/src/lib/log/tests/logger_manager_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2021 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -444,3 +445,67 @@ TEST_F(LoggerManagerTest, logDuplicatedMessages) { LoggerManager::logDuplicatedMessages(); ASSERT_EQ(0, MessageInitializer::getDuplicates().size()); } + +// Check that output options can be inherited. +TEST_F(LoggerManagerTest, outputOptionsInheritance) { + LoggerManager manager; + SpecificationForFileLogger file_spec; + vector specs; + + // Create the root logger configuration with a file output option. + string root_name(getRootLoggerName()); + LoggerSpecification root_spec(root_name); + OutputOption root_option; + root_option.destination = OutputOption::DEST_FILE; + root_option.filename = file_spec.getFileName(); + root_option.pattern = "%p %m\n"; + root_spec.addOutputOption(root_option); + specs.push_back(root_spec); + + // Create a child logger configuration without any output options. + // It should inherit the output option from the root logger. + string foo_name(root_name + ".foo"); + LoggerSpecification foo_spec(foo_name); + specs.push_back(foo_spec); + + // Create another child logger configuration with a console output option. + string bar_name(root_name + ".bar"); + LoggerSpecification bar_spec(bar_name); + OutputOption bar_option; + bar_option.destination = OutputOption::DEST_CONSOLE; + bar_option.pattern = "%p %m\n"; + bar_spec.addOutputOption(bar_option); + specs.push_back(bar_spec); + + // Check the number of output options for each specification. + EXPECT_EQ(root_spec.optionCount(), 1); + EXPECT_EQ(foo_spec.optionCount(), 0); + EXPECT_EQ(bar_spec.optionCount(), 1); + + // Process all the specifications. + manager.process(specs.begin(), specs.end()); + + // Log two messages each. + Logger root_logger(root_name.c_str()); + Logger foo_logger(foo_name.c_str()); + Logger bar_logger(bar_name.c_str()); + LOG_INFO(root_logger, "from root logger 1"); + LOG_INFO(foo_logger, "from foo logger 1"); + LOG_INFO(bar_logger, "from bar logger 1"); + LOG_INFO(root_logger, "from root logger 2"); + LOG_INFO(foo_logger, "from foo logger 2"); + LOG_INFO(bar_logger, "from bar logger 2"); + + // Check that root and foo were logged to file and that bar which + // had an explicit console configuration did not. + std::ifstream ifs(file_spec.getFileName()); + std::stringstream s; + s << ifs.rdbuf(); + std::string const result(s.str()); + EXPECT_NE(result.find("INFO from root logger 1"), string::npos); + EXPECT_NE(result.find("INFO from foo logger 1"), string::npos); + EXPECT_EQ(result.find("INFO from bar logger 1"), string::npos); + EXPECT_NE(result.find("INFO from root logger 2"), string::npos); + EXPECT_NE(result.find("INFO from foo logger 2"), string::npos); + EXPECT_EQ(result.find("INFO from bar logger 2"), string::npos); +}