conf: Add log4cxx path to conf file

refs: #1950

Change-Id: I51f9217c6ee40fd49a53d5f8294b60fb306e82ee
diff --git a/log4cxx.properties.sample.in b/log4cxx.properties.sample.in
new file mode 100644
index 0000000..14e83a8
--- /dev/null
+++ b/log4cxx.properties.sample.in
@@ -0,0 +1,11 @@
+log4j.rootLogger=DEBUG, Rolling
+
+log4j.appender.Rolling=org.apache.log4j.rolling.RollingFileAppender
+log4j.appender.Rolling.File=/tmp/testnlsrproperties.log
+log4j.appender.Rolling.MaxSize=1MB
+log4j.appender.Rolling.MaxBackupIndex=10
+
+log4j.appender.Rolling.layout=PatternLayout
+log4j.appender.Rolling.layout.ContextPrinting=enabled
+log4j.appender.Rolling.layout.DateFormat=ISO8601
+log4j.appender.Rolling.layout.ConversionPattern=%date{yyyyMMddHHmmssSSS} %p: [%c] %m%n
\ No newline at end of file
diff --git a/nlsr.conf b/nlsr.conf
index 52ce870..31d9633 100644
--- a/nlsr.conf
+++ b/nlsr.conf
@@ -29,8 +29,9 @@
 
   log-level  INFO
 
-  log-dir /var/log/nlsr/  ; path for log directory (Absolute path)
-  seq-dir /var/lib/nlsr/  ; path for sequence directory (Absolute path)
+  log-dir       /var/log/nlsr/         ; path for log directory (Absolute path)
+  seq-dir       /var/lib/nlsr/         ; path for sequence directory (Absolute path)
+  ;log4cxx-conf /path/to/log4cxx-conf  ; path for log4cxx configuration file (Absolute path)
 }
 
 ; the neighbors section contains the configuration for router's neighbors and hello's behavior
diff --git a/src/conf-file-processor.cpp b/src/conf-file-processor.cpp
index de36941..b3067a3 100644
--- a/src/conf-file-processor.cpp
+++ b/src/conf-file-processor.cpp
@@ -348,7 +348,7 @@
       }
     }
     else {
-      std::cerr << "Log directory provided does not exists" << std::endl;
+      std::cerr << "Provided log directory <" << logDir << "> does not exist" << std::endl;
       return false;
     }
   }
@@ -357,6 +357,7 @@
     std::cerr << ex.what() << std::endl;
     return false;
   }
+
   try {
     std::string seqDir = section.get<string>("seq-dir");
     if (boost::filesystem::exists(seqDir)) {
@@ -381,7 +382,7 @@
       }
     }
     else {
-      std::cerr << "Seq directory provided does not exists" << std::endl;
+      std::cerr << "Provided sequence directory <" << seqDir << "> does not exist" << std::endl;
       return false;
     }
   }
@@ -391,6 +392,28 @@
     return false;
   }
 
+  try {
+    std::string log4cxxPath = section.get<string>("log4cxx-conf");
+
+    if (log4cxxPath == "") {
+      std::cerr << "No value provided for log4cxx-conf" << std::endl;
+      return false;
+    }
+
+    if (boost::filesystem::exists(log4cxxPath)) {
+      m_nlsr.getConfParameter().setLog4CxxConfPath(log4cxxPath);
+    }
+    else {
+      std::cerr << "Provided path for log4cxx-conf <" << log4cxxPath
+                << "> does not exist" << std::endl;
+
+      return false;
+    }
+  }
+  catch (const std::exception& ex) {
+    // Variable is optional so default configuration will be used; continue processing file
+  }
+
   return true;
 }
 
@@ -648,4 +671,4 @@
   return true;
 }
 
-} // namespace nlsr
+} // namespace nlsr
\ No newline at end of file
diff --git a/src/conf-parameter.cpp b/src/conf-parameter.cpp
index 763d771..30f0cf4 100644
--- a/src/conf-parameter.cpp
+++ b/src/conf-parameter.cpp
@@ -20,6 +20,7 @@
  * \author A K M Mahmudul Hoque <ahoque1@memphis.edu>
  *
  **/
+
 #include "conf-parameter.hpp"
 #include "logger.hpp"
 
diff --git a/src/conf-parameter.hpp b/src/conf-parameter.hpp
index 02eee7b..e25c4d3 100644
--- a/src/conf-parameter.hpp
+++ b/src/conf-parameter.hpp
@@ -111,6 +111,7 @@
     , m_corR(0)
     , m_corTheta(0)
     , m_maxFacesPerPrefix(MAX_FACES_PER_PREFIX_MIN)
+    , m_isLog4cxxConfAvailable(false)
   {
   }
 
@@ -381,6 +382,25 @@
     return m_seqFileDir;
   }
 
+  bool
+  isLog4CxxConfAvailable() const
+  {
+    return m_isLog4cxxConfAvailable;
+  }
+
+  void
+  setLog4CxxConfPath(const std::string& path)
+  {
+    m_log4CxxConfPath = path;
+    m_isLog4cxxConfAvailable = true;
+  }
+
+  const std::string&
+  getLog4CxxConfPath() const
+  {
+    return m_log4CxxConfPath;
+  }
+
   void
   writeLog();
 
@@ -420,6 +440,8 @@
   std::string m_logDir;
   std::string m_seqFileDir;
 
+  bool m_isLog4cxxConfAvailable;
+  std::string m_log4CxxConfPath;
 };
 
 } // namespace nlsr
diff --git a/src/logger.cpp b/src/logger.cpp
index a82f5a9..6451a32 100644
--- a/src/logger.cpp
+++ b/src/logger.cpp
@@ -22,16 +22,30 @@
  **/
 #include <log4cxx/logger.h>
 #include <log4cxx/basicconfigurator.h>
+#include <log4cxx/xml/domconfigurator.h>
+#include <log4cxx/propertyconfigurator.h>
 #include <log4cxx/patternlayout.h>
 #include <log4cxx/level.h>
 #include <log4cxx/helpers/exception.h>
 #include <log4cxx/rollingfileappender.h>
 
 #include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
 
 #include "logger.hpp"
 
 void
+INIT_LOG4CXX(const std::string& log4cxxConfPath)
+{
+  if (boost::filesystem::path(log4cxxConfPath).extension().string() == ".xml") {
+    log4cxx::xml::DOMConfigurator::configure(log4cxxConfPath);
+  }
+  else {
+    log4cxx::PropertyConfigurator::configure(log4cxxConfPath);
+  }
+}
+
+void
 INIT_LOGGERS(const std::string& logDir, const std::string& logLevel)
 {
   static bool configured = false;
@@ -71,4 +85,4 @@
          boost::iequals(logLevel, "debug") || boost::iequals(logLevel, "info")  ||
          boost::iequals(logLevel, "warn")  || boost::iequals(logLevel, "error") ||
          boost::iequals(logLevel, "none");
-}
+}
\ No newline at end of file
diff --git a/src/logger.hpp b/src/logger.hpp
index 267f30f..2a8e907 100644
--- a/src/logger.hpp
+++ b/src/logger.hpp
@@ -20,6 +20,7 @@
  * \author A K M Mahmudul Hoque <ahoque1@memphis.edu>
  *
  **/
+
 #ifndef NLSR_LOGGER_HPP
 #define NLSR_LOGGER_HPP
 
@@ -49,6 +50,9 @@
 void
 INIT_LOGGERS(const std::string& logDir, const std::string& logLevel);
 
+void
+INIT_LOG4CXX(const std::string& log4cxxConfPath);
+
 bool
 isValidLogLevel(const std::string& logLevel);
 
diff --git a/src/main.cpp b/src/main.cpp
index a21b0fd..45772aa 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -66,13 +66,20 @@
         return EXIT_FAILURE;
       }
   }
+
   ConfFileProcessor cfp(nlsr, nlsr.getConfFileName());
   if (!cfp.processConfFile()) {
     std::cerr << "Error in configuration file processing! Exiting from NLSR" << std::endl;
     return EXIT_FAILURE;
   }
 
-  INIT_LOGGERS(nlsr.getConfParameter().getLogDir(), nlsr.getConfParameter().getLogLevel());
+  if (nlsr.getConfParameter().isLog4CxxConfAvailable()) {
+    INIT_LOG4CXX(nlsr.getConfParameter().getLog4CxxConfPath());
+  }
+  else {
+    INIT_LOGGERS(nlsr.getConfParameter().getLogDir(), nlsr.getConfParameter().getLogLevel());
+  }
+
   INIT_LOGGER("Main");
 
   nlsr.initialize();
@@ -96,4 +103,4 @@
 main(int32_t argc, char** argv)
 {
   return nlsr::main(argc, argv);
-}
+}
\ No newline at end of file
diff --git a/src/nlsr.cpp b/src/nlsr.cpp
index a66e6aa..19bb3cf 100644
--- a/src/nlsr.cpp
+++ b/src/nlsr.cpp
@@ -20,6 +20,7 @@
  * \author A K M Mahmudul Hoque <ahoque1@memphis.edu>
  *
  **/
+
 #include <cstdlib>
 #include <string>
 #include <sstream>
diff --git a/tests/test-conf-file-processor.cpp b/tests/test-conf-file-processor.cpp
index 5b70a65..25d1433 100644
--- a/tests/test-conf-file-processor.cpp
+++ b/tests/test-conf-file-processor.cpp
@@ -23,11 +23,15 @@
 
 #include "test-common.hpp"
 #include "dummy-face.hpp"
+#include "logger.hpp"
 
 #include <fstream>
 #include "conf-file-processor.hpp"
 #include "nlsr.hpp"
+
 #include <boost/test/unit_test.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/algorithm/string.hpp>
 
 namespace nlsr {
 namespace test {
@@ -49,6 +53,23 @@
   "  seq-dir /tmp\n"
   "}\n\n";
 
+const std::string LOG4CXX_PLACEHOLDER = "$LOG4CXX$";
+
+const std::string SECTION_GENERAL_WITH_LOG4CXX =
+  "general\n"
+  "{\n"
+  "  network /ndn/\n"
+  "  site /memphis.edu/\n"
+  "  router /cs/pollux/\n"
+  "  lsa-refresh-time 1800\n"
+  "  lsa-interest-lifetime 3\n"
+  "  router-dead-interval 86400\n"
+  "  log-level  INFO\n"
+  "  log-dir /tmp\n"
+  "  seq-dir /tmp\n"
+  "  log4cxx-conf " + LOG4CXX_PLACEHOLDER + "\n"
+  "}\n\n";
+
 const std::string SECTION_NEIGHBORS =
   "neighbors\n"
   "{\n"
@@ -104,6 +125,8 @@
 const std::string CONFIG_LINK_STATE = SECTION_GENERAL + SECTION_NEIGHBORS +
                                       SECTION_HYPERBOLIC_OFF + SECTION_FIB + SECTION_ADVERTISING;
 
+const std::string CONFIG_LOG4CXX = SECTION_GENERAL_WITH_LOG4CXX;
+
 const std::string CONFIG_HYPERBOLIC = SECTION_GENERAL + SECTION_NEIGHBORS +
                                       SECTION_HYPERBOLIC_ON + SECTION_FIB + SECTION_ADVERTISING;
 
@@ -114,12 +137,18 @@
     : face(ndn::makeDummyFace())
     , nlsr(g_ioService, g_scheduler, ndn::ref(*face))
     , CONFIG_FILE("unit-test-nlsr.conf")
+    , m_logConfigFileName(boost::filesystem::unique_path().native())
+    , m_logFileName(boost::filesystem::unique_path().native())
   {
   }
 
   ~ConfFileProcessorFixture()
   {
     remove("unit-test-nlsr.conf");
+    remove("/tmp/unit-test-log4cxx.xml");
+
+    boost::filesystem::remove(boost::filesystem::path(getLogConfigFileName()));
+    boost::filesystem::remove(boost::filesystem::path(getLogFileName()));
   }
 
   bool processConfigurationString(std::string confString)
@@ -133,12 +162,47 @@
     return processor.processConfFile();
   }
 
+  void
+  verifyOutputLog4cxx(const std::string expected[], size_t nExpected)
+  {
+    std::ifstream is(getLogFileName().c_str());
+    std::string buffer((std::istreambuf_iterator<char>(is)),
+                       (std::istreambuf_iterator<char>()));
+
+    std::vector<std::string> components;
+    boost::split(components, buffer, boost::is_any_of(" ,\n"));
+
+    // expected + number of timestamps (one per log statement) + trailing newline of last statement
+    BOOST_REQUIRE_EQUAL(components.size(), nExpected);
+
+    for (size_t i = 0; i < nExpected; ++i) {
+      if (expected[i] == "")
+        continue;
+
+      BOOST_CHECK_EQUAL(components[i], expected[i]);
+    }
+  }
+
+  const std::string&
+  getLogConfigFileName()
+  {
+    return m_logConfigFileName;
+  }
+
+  const std::string&
+  getLogFileName()
+  {
+    return m_logFileName;
+  }
+
 public:
   shared_ptr<ndn::DummyFace> face;
   Nlsr nlsr;
 
 private:
   const std::string CONFIG_FILE;
+  std::string m_logConfigFileName;
+  std::string m_logFileName;
 };
 
 BOOST_FIXTURE_TEST_SUITE(TestConfFileProcessor, ConfFileProcessorFixture)
@@ -146,7 +210,6 @@
 BOOST_AUTO_TEST_CASE(LinkState)
 {
   processConfigurationString(CONFIG_LINK_STATE);
-
   ConfParameter& conf = nlsr.getConfParameter();
   conf.buildRouterPrefix();
 
@@ -195,6 +258,81 @@
   BOOST_CHECK_EQUAL(nlsr.getNamePrefixList().getSize(), 2);
 }
 
+BOOST_AUTO_TEST_CASE(Log4cxxFileExists)
+{
+  std::string configPath = boost::filesystem::unique_path().native();
+
+  std::ofstream log4cxxConfFile;
+  log4cxxConfFile.open(configPath);
+  log4cxxConfFile.close();
+
+  std::string config = CONFIG_LOG4CXX;
+  boost::replace_all(config, LOG4CXX_PLACEHOLDER, configPath);
+
+  BOOST_CHECK_EQUAL(processConfigurationString(config), true);
+
+  ConfParameter& conf = nlsr.getConfParameter();
+  BOOST_CHECK_EQUAL(conf.getLog4CxxConfPath(), configPath);
+  BOOST_CHECK_EQUAL(conf.isLog4CxxConfAvailable(), true);
+
+  boost::filesystem::remove(boost::filesystem::path(configPath));
+}
+
+BOOST_AUTO_TEST_CASE(Log4cxxFileDoesNotExist)
+{
+  std::string configPath = boost::filesystem::unique_path().native();
+
+  std::string config = CONFIG_LOG4CXX;
+  boost::replace_all(config, LOG4CXX_PLACEHOLDER, configPath);
+
+  BOOST_CHECK_EQUAL(processConfigurationString(config), false);
+}
+
+BOOST_AUTO_TEST_CASE(Log4cxxNoValue)
+{
+  std::string config = CONFIG_LOG4CXX;
+  boost::replace_all(config, LOG4CXX_PLACEHOLDER, "");
+
+  BOOST_CHECK_EQUAL(processConfigurationString(config), false);
+}
+
+BOOST_AUTO_TEST_CASE(Log4cxxTestCase)
+{
+  {
+    std::ofstream of(getLogConfigFileName().c_str());
+    of << "log4j.rootLogger=TRACE, FILE\n"
+       << "log4j.appender.FILE=org.apache.log4j.FileAppender\n"
+       << "log4j.appender.FILE.layout=org.apache.log4j.PatternLayout\n"
+       << "log4j.appender.FILE.File=" << getLogFileName() << "\n"
+       << "log4j.appender.FILE.ImmediateFlush=true\n"
+       << "log4j.appender.FILE.layout.ConversionPattern=%d{HH:mm:ss} %p %c{1} - %m%n\n";
+  }
+
+  INIT_LOG4CXX(getLogConfigFileName());
+
+  INIT_LOGGER("DefaultConfig");
+
+  _LOG_TRACE("trace-message-JHGFDSR^1");
+  _LOG_DEBUG("debug-message-IGg2474fdksd-fo-" << 15 << 16 << 17);
+  _LOG_INFO("info-message-Jjxjshj13");
+  _LOG_WARN("warning-message-XXXhdhd11" << 1 <<"x");
+  _LOG_ERROR("error-message-!#$&^%$#@");
+  _LOG_FATAL("fatal-message-JJSjaamcng");
+
+  const std::string EXPECTED[] =
+    {
+      "", "TRACE", "DefaultConfig", "-", "trace-message-JHGFDSR^1",
+      "", "DEBUG", "DefaultConfig", "-", "debug-message-IGg2474fdksd-fo-151617",
+      "", "INFO",  "DefaultConfig", "-", "info-message-Jjxjshj13",
+      "", "WARN",  "DefaultConfig", "-", "warning-message-XXXhdhd111x",
+      "", "ERROR", "DefaultConfig", "-", "error-message-!#$&^%$#@",
+      "", "FATAL", "DefaultConfig", "-", "fatal-message-JJSjaamcng",
+      "",
+    };
+
+  verifyOutputLog4cxx(EXPECTED, sizeof(EXPECTED) / sizeof(std::string));
+}
+
 BOOST_AUTO_TEST_CASE(Hyperbolic)
 {
   processConfigurationString(CONFIG_HYPERBOLIC);
@@ -255,4 +393,4 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 } //namespace test
-} //namespace nlsr
+} //namespace nlsr
\ No newline at end of file