logger_manager_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. // Copyright (C) 2011, 2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <stdio.h>
  15. #include <unistd.h>
  16. #include <fstream>
  17. #include <iostream>
  18. #include <string>
  19. #include <gtest/gtest.h>
  20. #include <boost/scoped_array.hpp>
  21. #include <boost/lexical_cast.hpp>
  22. #include <exceptions/exceptions.h>
  23. #include <log/macros.h>
  24. #include <log/log_messages.h>
  25. #include <log/logger.h>
  26. #include <log/logger_level.h>
  27. #include <log/logger_manager.h>
  28. #include <log/logger_specification.h>
  29. #include <log/message_initializer.h>
  30. #include <log/output_option.h>
  31. #include "tempdir.h"
  32. #include <sys/types.h>
  33. #include <regex.h>
  34. using namespace isc;
  35. using namespace isc::log;
  36. using namespace std;
  37. /// \brief LoggerManager Test
  38. class LoggerManagerTest : public ::testing::Test {
  39. public:
  40. LoggerManagerTest() {
  41. // Initialization of logging is done in main()
  42. }
  43. ~LoggerManagerTest() {
  44. LoggerManager::reset();
  45. }
  46. };
  47. // Convenience class to create the specification for the logger "filelogger",
  48. // which, as the name suggests, logs to a file. It remembers the file name and
  49. // deletes the file when instance of the class is destroyed.
  50. class SpecificationForFileLogger {
  51. public:
  52. // Constructor - allocate file and create the specification object
  53. SpecificationForFileLogger() : spec_(), name_(createTempFilename()),
  54. logname_("filelogger") {
  55. // Set the output to a temporary file.
  56. OutputOption option;
  57. option.destination = OutputOption::DEST_FILE;
  58. option.filename = name_;
  59. // Set target output to the file logger. The defaults indicate
  60. // INFO severity.
  61. spec_.setName(logname_);
  62. spec_.addOutputOption(option);
  63. }
  64. // Destructor, remove the file. This is only a test, so ignore failures
  65. ~SpecificationForFileLogger() {
  66. if (! name_.empty()) {
  67. static_cast<void>(remove(name_.c_str()));
  68. // Depending on the log4cplus version, a lock file may also be
  69. // created.
  70. static_cast<void>(remove((name_ + ".lock").c_str()));
  71. }
  72. }
  73. // Return reference to the logging specification for this loggger
  74. LoggerSpecification& getSpecification() {
  75. return spec_;
  76. }
  77. // Return name of the logger
  78. string getLoggerName() const {
  79. return logname_;
  80. }
  81. // Return name of the file
  82. string getFileName() const {
  83. return name_;
  84. }
  85. // Create temporary filename
  86. //
  87. // The compiler warns against tmpnam() and suggests mkstemp instead.
  88. // Unfortunately, this creates the filename and opens it. So we need to
  89. // close and delete the file before returning the name. Also, the name
  90. // is based on the template supplied and the name of the temporary
  91. // directory may vary between systems. So translate TMPDIR and if that
  92. // does not exist, use /tmp.
  93. //
  94. // \return Temporary file name
  95. static std::string createTempFilename() {
  96. string filename = TEMP_DIR + "/kea_logger_manager_test_XXXXXX";
  97. // Copy into writeable storage for the call to mkstemp
  98. boost::scoped_array<char> tname(new char[filename.size() + 1]);
  99. strcpy(tname.get(), filename.c_str());
  100. // Create file, close and delete it, and store the name for later.
  101. // There is still a race condition here, albeit a small one.
  102. int filenum = mkstemp(tname.get());
  103. if (filenum == -1) {
  104. isc_throw(Exception, "Unable to obtain unique filename");
  105. }
  106. close(filenum);
  107. return (string(tname.get()));
  108. }
  109. private:
  110. LoggerSpecification spec_; // Specification for this file logger
  111. string name_; // Name of the output file
  112. string logname_; // Name of this logger
  113. };
  114. // Convenience function to read an output log file and check that each line
  115. // contains the expected message ID
  116. //
  117. // \param filename Name of the file to check
  118. // \param start Iterator pointing to first expected message ID
  119. // \param finish Iterator pointing to last expected message ID
  120. template <typename T>
  121. void checkFileContents(const std::string& filename, T start, T finish) {
  122. // Access the file for input
  123. ifstream infile(filename.c_str());
  124. if (! infile.good()) {
  125. FAIL() << "Unable to open the logging file " << filename;
  126. }
  127. // Iterate round the expected message IDs and check that they appear in
  128. // the string.
  129. string line; // Line read from the file
  130. T i = start; // Iterator
  131. getline(infile, line);
  132. int lineno = 1;
  133. while ((i != finish) && (infile.good())) {
  134. // Check that the message ID appears in the line.
  135. EXPECT_TRUE(line.find(string(*i)) != string::npos)
  136. << "Expected to find " << string(*i) << " on line " << lineno
  137. << " of logging file " << filename;
  138. // Go for the next line
  139. ++i;
  140. getline(infile, line);
  141. ++lineno;
  142. }
  143. // Why did the loop end?
  144. EXPECT_TRUE(i == finish) << "Did not reach the end of the message ID list";
  145. EXPECT_TRUE(infile.eof()) << "Did not reach the end of the logging file";
  146. // File will close when the instream is deleted at the end of this
  147. // function.
  148. }
  149. // Check that the logger correctly creates something logging to a file.
  150. TEST_F(LoggerManagerTest, FileLogger) {
  151. // Create a specification for the file logger and use the manager to
  152. // connect the "filelogger" logger to it.
  153. SpecificationForFileLogger file_spec;
  154. // For the first test, we want to check that the file is created
  155. // if it does not already exist. So delete the temporary file before
  156. // logging the first message.
  157. remove(file_spec.getFileName().c_str());
  158. // Set up the file appenders.
  159. LoggerManager manager;
  160. manager.process(file_spec.getSpecification());
  161. // Try logging to the file. Local scope is set to ensure that the logger
  162. // is destroyed before we reset the global logging. We record what we
  163. // put in the file for a later comparison.
  164. vector<MessageID> ids;
  165. {
  166. // Scope-limit the logger to ensure it is destroyed after the brief
  167. // check. This adds weight to the idea that the logger will not
  168. // keep the file open.
  169. Logger logger(file_spec.getLoggerName().c_str());
  170. LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
  171. ids.push_back(LOG_DUPLICATE_MESSAGE_ID);
  172. LOG_FATAL(logger, LOG_DUPLICATE_NAMESPACE).arg("test");
  173. ids.push_back(LOG_DUPLICATE_NAMESPACE);
  174. }
  175. LoggerManager::reset();
  176. // At this point, the output file should contain two lines with messages
  177. // LOG_DUPLICATE_MESSAGE_ID and LOG_DUPLICATE_NAMESPACE messages - test this.
  178. checkFileContents(file_spec.getFileName(), ids.begin(), ids.end());
  179. // Re-open the file (we have to assume that it was closed when we
  180. // reset the logger - there is no easy way to check) and check that
  181. // new messages are appended to it. We use the alternative
  182. // invocation of process() here to check it works.
  183. vector<LoggerSpecification> spec(1, file_spec.getSpecification());
  184. manager.process(spec.begin(), spec.end());
  185. // Create a new instance of the logger and log three more messages.
  186. Logger logger(file_spec.getLoggerName().c_str());
  187. LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg("test");
  188. ids.push_back(LOG_NO_SUCH_MESSAGE);
  189. LOG_FATAL(logger, LOG_INVALID_MESSAGE_ID).arg("test").arg("test2");
  190. ids.push_back(LOG_INVALID_MESSAGE_ID);
  191. LOG_FATAL(logger, LOG_NO_MESSAGE_ID).arg("42");
  192. ids.push_back(LOG_NO_MESSAGE_ID);
  193. // Close the file and check again
  194. LoggerManager::reset();
  195. checkFileContents(file_spec.getFileName(), ids.begin(), ids.end());
  196. }
  197. // Check if the file rolls over when it gets above a certain size.
  198. TEST_F(LoggerManagerTest, FileSizeRollover) {
  199. // Set to a suitable minimum that log4cplus can copy with
  200. static const size_t SIZE_LIMIT = 204800;
  201. // Set up the name of the file.
  202. SpecificationForFileLogger file_spec;
  203. LoggerSpecification& spec = file_spec.getSpecification();
  204. // Expand the option to ensure that a maximum version size is set.
  205. LoggerSpecification::iterator opt = spec.begin();
  206. EXPECT_TRUE(opt != spec.end());
  207. opt->maxsize = SIZE_LIMIT; // Bytes
  208. opt->maxver = 2;
  209. // The current current output file does not exist (the creation of file_spec
  210. // ensures that. Check that previous versions don't either.
  211. vector<string> prev_name;
  212. for (int i = 0; i < 3; ++i) {
  213. prev_name.push_back(file_spec.getFileName() + "." +
  214. boost::lexical_cast<string>(i + 1));
  215. (void) remove(prev_name[i].c_str());
  216. }
  217. // Generate an argument for a message that ensures that the message when
  218. // logged will be over that size.
  219. string big_arg(SIZE_LIMIT, 'x');
  220. // Set up the file logger
  221. LoggerManager manager;
  222. manager.process(spec);
  223. // Log the message twice using different message IDs. This should generate
  224. // three files as for the log4cplus implementation, the files appear to
  225. // be rolled after the message is logged.
  226. {
  227. Logger logger(file_spec.getLoggerName().c_str());
  228. LOG_FATAL(logger, LOG_NO_SUCH_MESSAGE).arg(big_arg);
  229. LOG_FATAL(logger, LOG_DUPLICATE_NAMESPACE).arg(big_arg);
  230. }
  231. // Check them.
  232. LoggerManager::reset(); // Ensure files are closed
  233. vector<MessageID> ids;
  234. ids.push_back(LOG_NO_SUCH_MESSAGE);
  235. checkFileContents(prev_name[1], ids.begin(), ids.end());
  236. ids.clear();
  237. ids.push_back(LOG_DUPLICATE_NAMESPACE);
  238. checkFileContents(prev_name[0], ids.begin(), ids.end());
  239. // Log another message and check that the files have rotated and that
  240. // a .3 version does not exist.
  241. manager.process(spec);
  242. {
  243. Logger logger(file_spec.getLoggerName().c_str());
  244. LOG_FATAL(logger, LOG_NO_MESSAGE_TEXT).arg("42").arg(big_arg);
  245. }
  246. LoggerManager::reset(); // Ensure files are closed
  247. // Check that the files have moved.
  248. ids.clear();
  249. ids.push_back(LOG_DUPLICATE_NAMESPACE);
  250. checkFileContents(prev_name[1], ids.begin(), ids.end());
  251. ids.clear();
  252. ids.push_back(LOG_NO_MESSAGE_TEXT);
  253. checkFileContents(prev_name[0], ids.begin(), ids.end());
  254. // ... and check that the .3 version does not exist.
  255. ifstream file3(prev_name[2].c_str(), ifstream::in);
  256. EXPECT_FALSE(file3.good());
  257. // Tidy up
  258. for (vector<string>::size_type i = 0; i < prev_name.size(); ++i) {
  259. (void) remove(prev_name[i].c_str());
  260. }
  261. }
  262. namespace { // begin unnamed namespace
  263. // When we begin to use C++11, we could replace use of POSIX API with
  264. // <regex>.
  265. class RegexHolder {
  266. public:
  267. RegexHolder(const char* expr, const int flags = REG_EXTENDED) {
  268. const int rc = regcomp(&regex_, expr, flags);
  269. if (rc) {
  270. regfree(&regex_);
  271. throw;
  272. }
  273. }
  274. ~RegexHolder() {
  275. regfree(&regex_);
  276. }
  277. regex_t* operator*() {
  278. return (&regex_);
  279. }
  280. private:
  281. regex_t regex_;
  282. };
  283. } // end of unnamed namespace
  284. // Check that the logger correctly outputs the full formatted layout
  285. // pattern.
  286. TEST_F(LoggerManagerTest, checkLayoutPattern) {
  287. // Create a specification for the file logger and use the manager to
  288. // connect the "filelogger" logger to it.
  289. SpecificationForFileLogger file_spec;
  290. // For the first test, we want to check that the file is created
  291. // if it does not already exist. So delete the temporary file before
  292. // logging the first message.
  293. remove(file_spec.getFileName().c_str());
  294. // Set up the file appenders.
  295. LoggerManager manager;
  296. manager.process(file_spec.getSpecification());
  297. // Try logging to the file. Local scope is set to ensure that the logger
  298. // is destroyed before we reset the global logging.
  299. {
  300. Logger logger(file_spec.getLoggerName().c_str());
  301. LOG_FATAL(logger, LOG_DUPLICATE_MESSAGE_ID).arg("test");
  302. }
  303. LoggerManager::reset();
  304. // Access the file for input
  305. const std::string& filename = file_spec.getFileName();
  306. ifstream infile(filename.c_str());
  307. if (! infile.good()) {
  308. FAIL() << "Unable to open the logging file " << filename;
  309. }
  310. std::string line;
  311. std::getline(infile, line);
  312. RegexHolder regex(// %D{%Y-%m-%d %H:%M:%S.%q}
  313. "^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}[[:space:]]"
  314. "[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\.[[:digit:]]+[[:space:]]"
  315. // %-5p
  316. "[[:alpha:]]{1,5}[[:space:]]"
  317. // [%c/%i]
  318. "\\[[[:alnum:]\\-\\.]+/[[:digit:]]+\\][[:space:]]"
  319. );
  320. const int re = regexec(*regex, line.c_str(), 0, NULL, 0);
  321. ASSERT_EQ(0, re)
  322. << "Logged message does not match expected layout pattern";
  323. }
  324. // Check that after calling the logDuplicatedMessages, the duplicated
  325. // messages are removed.
  326. TEST_F(LoggerManagerTest, logDuplicatedMessages) {
  327. // Original set should not have duplicates.
  328. ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
  329. // This just defines 1, but we'll add it a number of times.
  330. const char* dupe[] = {
  331. "DUPE", "dupe",
  332. NULL
  333. };
  334. const MessageInitializer init_message_initializer_1(dupe);
  335. const MessageInitializer init_message_initializer_2(dupe);
  336. MessageInitializer::loadDictionary();
  337. // Should have a duplicate now.
  338. ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
  339. // The logDuplicatedMessages, besides logging, should also remove the
  340. // duplicates.
  341. LoggerManager::logDuplicatedMessages();
  342. ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
  343. }