logging_unittest.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. // Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <config.h>
  7. #include <cc/data.h>
  8. #include <config/module_spec.h>
  9. #include <dhcpsrv/dhcpsrv_log.h>
  10. #include <dhcpsrv/logging.h>
  11. #include <exceptions/exceptions.h>
  12. #include <log/logger_support.h>
  13. #include <testutils/io_utils.h>
  14. #include <gtest/gtest.h>
  15. using namespace isc;
  16. using namespace isc::dhcp;
  17. using namespace isc::data;
  18. namespace {
  19. /// @brief Logging Test Fixture Class
  20. ///
  21. /// Trivial class that ensures that the logging is reset to its defaults after
  22. /// each test. Strictly speaking this only resets the testing root logger (which
  23. /// has the name "kea") but as the only other logger mentioned here ("wombat")
  24. /// is not used elsewhere, that is sufficient.
  25. class LoggingTest : public ::testing::Test {
  26. public:
  27. /// @brief Constructor
  28. LoggingTest() {}
  29. /// @brief Destructor
  30. ///
  31. /// Reset root logger back to defaults.
  32. ~LoggingTest() {
  33. isc::log::setDefaultLoggingOutput();
  34. wipeFiles();
  35. }
  36. /// @brief Generates a log file name suffixed with a rotation number
  37. /// @param rotation number to the append to the end of the file
  38. std::string logName(int rotation) {
  39. std::ostringstream os;
  40. os << LOG_FILE_NAME << "." << rotation;
  41. return (os.str());
  42. }
  43. /// @brief Removes the base log file name and 1 rotation
  44. void wipeFiles() {
  45. static_cast<void>(remove(LOG_FILE_NAME));
  46. static_cast<void>(remove(logName(1).c_str()));
  47. }
  48. static const char* LOG_FILE_NAME;
  49. };
  50. const char* LoggingTest::LOG_FILE_NAME = "kea.test.log";
  51. // Tests that the spec file is valid.
  52. TEST_F(LoggingTest, basicSpec) {
  53. std::string specfile = std::string(LOGGING_SPEC_FILE);
  54. ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile));
  55. }
  56. // Checks that the constructor is able to process specified storage properly.
  57. TEST_F(LoggingTest, constructor) {
  58. SrvConfigPtr null_ptr;
  59. EXPECT_THROW(LogConfigParser parser(null_ptr), BadValue);
  60. SrvConfigPtr nonnull(new SrvConfig());
  61. EXPECT_NO_THROW(LogConfigParser parser(nonnull));
  62. }
  63. // Checks if the LogConfigParser class is able to transform JSON structures
  64. // into Configuration usable by log4cplus. This test checks for output
  65. // configured to stdout on debug level.
  66. TEST_F(LoggingTest, parsingConsoleOutput) {
  67. const char* config_txt =
  68. "{ \"loggers\": ["
  69. " {"
  70. " \"name\": \"kea\","
  71. " \"output_options\": ["
  72. " {"
  73. " \"output\": \"stdout\","
  74. " \"flush\": true"
  75. " }"
  76. " ],"
  77. " \"debuglevel\": 99,"
  78. " \"severity\": \"DEBUG\""
  79. " }"
  80. "]}";
  81. SrvConfigPtr storage(new SrvConfig());
  82. LogConfigParser parser(storage);
  83. // We need to parse properly formed JSON and then extract
  84. // "loggers" element from it. For some reason fromJSON is
  85. // throwing at opening square bracket
  86. ConstElementPtr config = Element::fromJSON(config_txt);
  87. config = config->get("loggers");
  88. EXPECT_NO_THROW(parser.parseConfiguration(config));
  89. ASSERT_EQ(1, storage->getLoggingInfo().size());
  90. EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
  91. EXPECT_EQ(99, storage->getLoggingInfo()[0].debuglevel_);
  92. EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[0].severity_);
  93. ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
  94. EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[0].output_);
  95. EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
  96. }
  97. // Checks if the LogConfigParser class is able to transform JSON structures
  98. // into Configuration usable by log4cplus. This test checks for output
  99. // configured to a file on INFO level.
  100. TEST_F(LoggingTest, parsingFile) {
  101. const char* config_txt =
  102. "{ \"loggers\": ["
  103. " {"
  104. " \"name\": \"kea\","
  105. " \"output_options\": ["
  106. " {"
  107. " \"output\": \"logfile.txt\""
  108. " }"
  109. " ],"
  110. " \"severity\": \"INFO\""
  111. " }"
  112. "]}";
  113. SrvConfigPtr storage(new SrvConfig());
  114. LogConfigParser parser(storage);
  115. // We need to parse properly formed JSON and then extract
  116. // "loggers" element from it. For some reason fromJSON is
  117. // throwing at opening square bracket
  118. ConstElementPtr config = Element::fromJSON(config_txt);
  119. config = config->get("loggers");
  120. EXPECT_NO_THROW(parser.parseConfiguration(config));
  121. ASSERT_EQ(1, storage->getLoggingInfo().size());
  122. EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
  123. EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
  124. EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
  125. ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
  126. EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
  127. // Default for immediate flush is true
  128. EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
  129. }
  130. // Checks if the LogConfigParser class is able to transform data structures
  131. // into Configuration usable by log4cplus. This test checks that more than
  132. // one logger can be configured.
  133. TEST_F(LoggingTest, multipleLoggers) {
  134. const char* config_txt =
  135. "{ \"loggers\": ["
  136. " {"
  137. " \"name\": \"kea\","
  138. " \"output_options\": ["
  139. " {"
  140. " \"output\": \"logfile.txt\","
  141. " \"flush\": true"
  142. " }"
  143. " ],"
  144. " \"severity\": \"INFO\""
  145. " },"
  146. " {"
  147. " \"name\": \"wombat\","
  148. " \"output_options\": ["
  149. " {"
  150. " \"output\": \"logfile2.txt\","
  151. " \"flush\": false"
  152. " }"
  153. " ],"
  154. " \"severity\": \"DEBUG\","
  155. " \"debuglevel\": 99"
  156. " }"
  157. "]}";
  158. SrvConfigPtr storage(new SrvConfig());
  159. LogConfigParser parser(storage);
  160. // We need to parse properly formed JSON and then extract
  161. // "loggers" element from it. For some reason fromJSON is
  162. // throwing at opening square bracket
  163. ConstElementPtr config = Element::fromJSON(config_txt);
  164. config = config->get("loggers");
  165. EXPECT_NO_THROW(parser.parseConfiguration(config));
  166. ASSERT_EQ(2, storage->getLoggingInfo().size());
  167. EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
  168. EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
  169. EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
  170. ASSERT_EQ(1, storage->getLoggingInfo()[0].destinations_.size());
  171. EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
  172. EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
  173. EXPECT_EQ("wombat", storage->getLoggingInfo()[1].name_);
  174. EXPECT_EQ(99, storage->getLoggingInfo()[1].debuglevel_);
  175. EXPECT_EQ(isc::log::DEBUG, storage->getLoggingInfo()[1].severity_);
  176. ASSERT_EQ(1, storage->getLoggingInfo()[1].destinations_.size());
  177. EXPECT_EQ("logfile2.txt" , storage->getLoggingInfo()[1].destinations_[0].output_);
  178. EXPECT_FALSE(storage->getLoggingInfo()[1].destinations_[0].flush_);
  179. }
  180. // Checks if the LogConfigParser class is able to transform data structures
  181. // into Configuration usable by log4cplus. This test checks that more than
  182. // one logging destination can be configured.
  183. TEST_F(LoggingTest, multipleLoggingDestinations) {
  184. const char* config_txt =
  185. "{ \"loggers\": ["
  186. " {"
  187. " \"name\": \"kea\","
  188. " \"output_options\": ["
  189. " {"
  190. " \"output\": \"logfile.txt\""
  191. " },"
  192. " {"
  193. " \"output\": \"stdout\""
  194. " }"
  195. " ],"
  196. " \"severity\": \"INFO\""
  197. " }"
  198. "]}";
  199. SrvConfigPtr storage(new SrvConfig());
  200. LogConfigParser parser(storage);
  201. // We need to parse properly formed JSON and then extract
  202. // "loggers" element from it. For some reason fromJSON is
  203. // throwing at opening square bracket
  204. ConstElementPtr config = Element::fromJSON(config_txt);
  205. config = config->get("loggers");
  206. EXPECT_NO_THROW(parser.parseConfiguration(config));
  207. ASSERT_EQ(1, storage->getLoggingInfo().size());
  208. EXPECT_EQ("kea", storage->getLoggingInfo()[0].name_);
  209. EXPECT_EQ(0, storage->getLoggingInfo()[0].debuglevel_);
  210. EXPECT_EQ(isc::log::INFO, storage->getLoggingInfo()[0].severity_);
  211. ASSERT_EQ(2, storage->getLoggingInfo()[0].destinations_.size());
  212. EXPECT_EQ("logfile.txt" , storage->getLoggingInfo()[0].destinations_[0].output_);
  213. EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[0].flush_);
  214. EXPECT_EQ("stdout" , storage->getLoggingInfo()[0].destinations_[1].output_);
  215. EXPECT_TRUE(storage->getLoggingInfo()[0].destinations_[1].flush_);
  216. }
  217. // Verifies that log rotation occurs when configured. We do not
  218. // worry about contents of the log files, only that rotation occurs.
  219. // Such details are tested in lib/log. This test verifies that
  220. // we can correcty configure logging such that rotation occurs as
  221. // expected.
  222. TEST_F(LoggingTest, logRotate) {
  223. int rotate_size = 2048;
  224. std::ostringstream os;
  225. os <<
  226. "{ \"loggers\": ["
  227. " {"
  228. " \"name\": \"kea\","
  229. " \"output_options\": ["
  230. " {"
  231. " \"output\": \""
  232. << LOG_FILE_NAME << "\"," <<
  233. " \"flush\": true,"
  234. " \"maxsize\":"
  235. << rotate_size << "," <<
  236. " \"maxver\": 1"
  237. " }"
  238. " ],"
  239. " \"debuglevel\": 99,"
  240. " \"severity\": \"DEBUG\""
  241. " }"
  242. "]}";
  243. // Make sure there aren't any left over.
  244. wipeFiles();
  245. // Create our server config container.
  246. SrvConfigPtr server_cfg(new SrvConfig());
  247. // LogConfigParser expects a list of loggers, so parse
  248. // the JSON text and extract the "loggers" element from it
  249. ConstElementPtr config = Element::fromJSON(os.str());
  250. config = config->get("loggers");
  251. // Parse the config and then apply it.
  252. LogConfigParser parser(server_cfg);
  253. ASSERT_NO_THROW(parser.parseConfiguration(config));
  254. ASSERT_NO_THROW(server_cfg->applyLoggingCfg());
  255. // Make sure we have the initial log file.
  256. ASSERT_TRUE(isc::test::fileExists(LOG_FILE_NAME));
  257. // Now generate a log we know will be large enough to force a rotation.
  258. // We borrow a one argument log message for the test.
  259. std::string big_arg(rotate_size, 'x');
  260. isc::log::Logger logger("kea");
  261. LOG_INFO(logger, DHCPSRV_CFGMGR_ADD_IFACE).arg(big_arg);
  262. // Make sure we now have a rotation file.
  263. EXPECT_TRUE(isc::test::fileExists(logName(1).c_str()));
  264. // Clean up.
  265. wipeFiles();
  266. }
  267. /// @todo Add tests for malformed logging configuration
  268. /// @todo There is no easy way to test applyConfiguration() and defaultLogging().
  269. /// To test them, it would require instrumenting log4cplus to actually fake
  270. /// the logging set up. Alternatively, we could develop set of test suites
  271. /// that check each logging destination separately (e.g. configure log file, then
  272. /// check if the file is indeed created or configure stdout destination, then
  273. /// swap console file descriptors and check that messages are really logged.
  274. };