summaryrefslogtreecommitdiffstats
path: root/src/lib/process/tests/log_parser_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/process/tests/log_parser_unittests.cc')
-rw-r--r--src/lib/process/tests/log_parser_unittests.cc512
1 files changed, 512 insertions, 0 deletions
diff --git a/src/lib/process/tests/log_parser_unittests.cc b/src/lib/process/tests/log_parser_unittests.cc
new file mode 100644
index 0000000..96f6278
--- /dev/null
+++ b/src/lib/process/tests/log_parser_unittests.cc
@@ -0,0 +1,512 @@
+// Copyright (C) 2014-2021 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <cc/data.h>
+#include <process/log_parser.h>
+#include <process/process_messages.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+#include <process/d_log.h>
+#include <testutils/io_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::process;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Logging Test Fixture Class
+///
+/// Trivial class that ensures that the logging is reset to its defaults after
+/// each test. Strictly speaking this only resets the testing root logger (which
+/// has the name "kea") but as the only other logger mentioned here ("wombat")
+/// is not used elsewhere, that is sufficient.
+class LoggingTest : public ::testing::Test {
+ public:
+ /// @brief Constructor
+ LoggingTest() {}
+
+ /// @brief Destructor
+ ///
+ /// Reset root logger back to defaults.
+ ~LoggingTest() {
+ isc::log::initLogger();
+ wipeFiles();
+ }
+
+ /// @brief Generates a log file name suffixed with a rotation number
+ /// @param rotation number to the append to the end of the file
+ std::string logName(int rotation) {
+ std::ostringstream os;
+ os << TEST_LOG_NAME << "." << rotation;
+ return (os.str());
+ }
+
+ /// @brief Removes the base log file name and 1 rotation
+ void wipeFiles() {
+ static_cast<void>(remove(TEST_LOG_NAME));
+ for (int i = 1; i < TEST_MAX_VERS + 1; ++i) {
+ static_cast<void>(remove(logName(i).c_str()));
+ }
+
+ // Remove the lock file
+ std::ostringstream os;
+ os << TEST_LOG_NAME << ".lock";
+ static_cast<void>(remove(os.str().c_str()));
+ }
+
+ /// @brief Name of the log file
+ static const char* TEST_LOG_NAME;
+
+ /// @brief Maximum log size
+ static const int TEST_MAX_SIZE;
+
+ /// @brief Maximum rotated log versions
+ static const int TEST_MAX_VERS;
+
+};
+
+const char* LoggingTest::TEST_LOG_NAME = "kea.test.log";
+const int LoggingTest::TEST_MAX_SIZE = 204800; // Smallest without disabling rotation
+const int LoggingTest::TEST_MAX_VERS = 2; // More than the default of 1
+
+// Checks that the constructor is able to process specified storage properly.
+TEST_F(LoggingTest, constructor) {
+
+ ConfigPtr null_ptr;
+ EXPECT_THROW(LogConfigParser parser(null_ptr), BadValue);
+
+ ConfigPtr nonnull(new ConfigBase());
+
+ EXPECT_NO_THROW(LogConfigParser parser(nonnull));
+}
+
+// Checks if the LogConfigParser class is able to transform JSON structures
+// into Configuration usable by log4cplus. This test checks for output
+// configured to stdout on debug level.
+TEST_F(LoggingTest, parsingConsoleOutput) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"flush\": true"
+ " }"
+ " ],"
+ " \"debuglevel\": 99,"
+ " \"severity\": \"DEBUG\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(99, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+}
+
+// Checks if the LogConfigParser class fails when the configuration
+// lacks a severity entry.
+TEST_F(LoggingTest, parsingNoSeverity) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"flush\": true"
+ " }"
+ " ],"
+ " \"debuglevel\": 99"
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_THROW(parser.parseConfiguration(config), BadValue);
+}
+
+// Checks if the LogConfigParser class is able to transform JSON structures
+// into Configuration usable by log4cplus. This test checks for output
+// configured to a file on INFO level.
+TEST_F(LoggingTest, parsingFile) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile.txt\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ // Default for immediate flush is true
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+
+ // Pattern should default to empty string.
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].pattern_.empty());
+}
+
+// Checks if the LogConfigParser class is able to transform data structures
+// into Configuration usable by log4cplus. This test checks that more than
+// one logger can be configured.
+TEST_F(LoggingTest, multipleLoggers) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile.txt\","
+ " \"flush\": true"
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " },"
+ " {"
+ " \"name\": \"wombat\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile2.txt\","
+ " \"flush\": false"
+ " }"
+ " ],"
+ " \"severity\": \"DEBUG\","
+ " \"debuglevel\": 99"
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(2, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+
+ EXPECT_EQ("wombat", storage->getLoggingInfo()[1].name_);
+ EXPECT_EQ(99, storage->getLoggingInfo()[1].debuglevel_);
+ EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[1].severity_);
+ ASSERT_EQ(1, storage->getLoggingInfo()[1].destinations_.size());
+ EXPECT_EQ("logfile2.txt" , storage->getLoggingInfo()[1].destinations_[0].output_);
+ EXPECT_FALSE(storage->getLoggingInfo()[1].destinations_[0].flush_);
+}
+
+// Checks if the LogConfigParser class is able to transform data structures
+// into Configuration usable by log4cplus. This test checks that more than
+// one logging destination can be configured.
+TEST_F(LoggingTest, multipleLoggingDestinations) {
+
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"logfile.txt\""
+ " },"
+ " {"
+ " \"output\": \"stdout\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+ ASSERT_EQ(2, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[1].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[1].flush_);
+}
+
+// Verifies that log rotation occurs when configured. We do not
+// worry about contents of the log files, only that rotation occurs.
+// Such details are tested in lib/log. This test verifies that
+// we can correctly configure logging such that rotation occurs as
+// expected.
+TEST_F(LoggingTest, logRotate) {
+ wipeFiles();
+
+ std::ostringstream os;
+ os <<
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \""
+ << TEST_LOG_NAME << "\"," <<
+ " \"flush\": true,"
+ " \"maxsize\":"
+ << TEST_MAX_SIZE << "," <<
+ " \"maxver\":"
+ << TEST_MAX_VERS <<
+ " }"
+ " ],"
+ " \"debuglevel\": 99,"
+ " \"severity\": \"DEBUG\""
+ " }"
+ "]}";
+
+ // Create our server config container.
+ ConfigPtr server_cfg(new ConfigBase());
+
+ // LogConfigParser expects a list of loggers, so parse
+ // the JSON text and extract the "loggers" element from it
+ ConstElementPtr config = Element::fromJSON(os.str());
+ config = config->get("loggers");
+
+ // Parse the config and then apply it.
+ LogConfigParser parser(server_cfg);
+ ASSERT_NO_THROW(parser.parseConfiguration(config));
+ ASSERT_NO_THROW(server_cfg->applyLoggingCfg());
+
+ EXPECT_EQ(TEST_MAX_SIZE, server_cfg->getLoggingInfo()[0].destinations_[0].maxsize_);
+ EXPECT_EQ(TEST_MAX_VERS, server_cfg->getLoggingInfo()[0].destinations_[0].maxver_);
+
+ // Make sure we have the initial log file.
+ ASSERT_TRUE(isc::test::fileExists(TEST_LOG_NAME));
+
+ // Now generate a log we know will be large enough to force a rotation.
+ // We borrow a one argument log message for the test.
+ std::string big_arg(TEST_MAX_SIZE, 'x');
+ isc::log::Logger logger("kea");
+
+ for (int i = 1; i < TEST_MAX_VERS + 1; i++) {
+ // Output the big log and make sure we get the expected rotation file.
+ LOG_INFO(logger, DCTL_CONFIG_COMPLETE).arg(big_arg);
+ EXPECT_TRUE(isc::test::fileExists(logName(i).c_str()));
+ }
+
+ // Clean up.
+ wipeFiles();
+}
+
+// Verifies that a valid output option,'pattern' paress correctly.
+TEST_F(LoggingTest, validPattern) {
+
+ // Note the backslash must be doubled in the pattern definition.
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"pattern\": \"mylog %m\\n\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_EQ(storage->getLoggingInfo()[0].destinations_[0].pattern_,
+ std::string("mylog %m\n"));
+}
+
+// Verifies that output option,'pattern', may be an empty string
+TEST_F(LoggingTest, emptyPattern) {
+ const char* config_txt =
+ "{ \"loggers\": ["
+ " {"
+ " \"name\": \"kea\","
+ " \"output_options\": ["
+ " {"
+ " \"output\": \"stdout\","
+ " \"pattern\": \"\""
+ " }"
+ " ],"
+ " \"severity\": \"INFO\""
+ " }"
+ "]}";
+
+ ConfigPtr storage(new ConfigBase());
+
+ LogConfigParser parser(storage);
+
+ // We need to parse properly formed JSON and then extract
+ // "loggers" element from it. For some reason fromJSON is
+ // throwing at opening square bracket
+ ConstElementPtr config = Element::fromJSON(config_txt);
+ config = config->get("loggers");
+
+ EXPECT_NO_THROW(parser.parseConfiguration(config));
+
+ ASSERT_EQ(1, storage->getLoggingInfo().size());
+
+ EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
+ EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
+
+ ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
+ EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
+ EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].pattern_.empty());
+}
+
+void testMaxSize(uint64_t maxsize_candidate, uint64_t expected_maxsize) {
+ std::string const logger(R"(
+ {
+ "loggers": [
+ {
+
+ "debuglevel": 99,
+ "name": "kea",
+ "output_options": [
+ {
+ "output": "kea.test.log",
+ "flush": true,
+ "maxsize": )" + std::to_string(maxsize_candidate) + R"(,
+ "maxver": 2
+ }
+ ],
+ "severity": "DEBUG"
+ }
+ ]
+ }
+ )");
+
+ // Create our server config container.
+ ConfigPtr server_cfg(boost::make_shared<ConfigBase>());
+
+ // LogConfigParser expects a list of loggers, so parse
+ // the JSON text and extract the "loggers" element from it
+ ConstElementPtr config(Element::fromJSON(logger));
+ config = config->get("loggers");
+
+ // Parse the config and then apply it.
+ LogConfigParser parser(server_cfg);
+ ASSERT_NO_THROW(parser.parseConfiguration(config));
+ ASSERT_NO_THROW(server_cfg->applyLoggingCfg());
+
+ EXPECT_EQ(server_cfg->getLoggingInfo()[0].destinations_[0].maxsize_,
+ expected_maxsize);
+}
+
+// Test that maxsize can be configured with high values.
+TEST_F(LoggingTest, maxsize) {
+ testMaxSize(TEST_MAX_SIZE, TEST_MAX_SIZE);
+ testMaxSize(std::numeric_limits<int32_t>::max(), std::numeric_limits<int32_t>::max());
+ testMaxSize(std::numeric_limits<uint32_t>::max(), std::numeric_limits<uint32_t>::max());
+ testMaxSize(1000LL * std::numeric_limits<int32_t>::max(), 1000LL * std::numeric_limits<int32_t>::max());
+ testMaxSize(1000000LL * std::numeric_limits<int32_t>::max(), 1000000LL * std::numeric_limits<int32_t>::max());
+}
+
+/// @todo Add tests for malformed logging configuration
+
+/// @todo There is no easy way to test applyConfiguration() and defaultLogging().
+/// To test them, it would require instrumenting log4cplus to actually fake
+/// the logging set up. Alternatively, we could develop set of test suites
+/// that check each logging destination separately (e.g. configure log file, then
+/// check if the file is indeed created or configure stdout destination, then
+/// swap console file descriptors and check that messages are really logged.
+
+} // namespace