core: change logging from environment variable to configuration file-based

GlobalConfigurationFixture uses unit-tests.conf to determine default
and module specific logging levels. Logging will gracefully default to
INFO if unit-tests.conf is not found. See unit-tests.conf.sample for a
sample configuration file.

refs: #1375, #1267

Change-Id: Ib0c4eb4149748e6658f94ef1afa23ddd3072c0fa
diff --git a/daemon/core/logger-factory.cpp b/daemon/core/logger-factory.cpp
new file mode 100644
index 0000000..4a353f1
--- /dev/null
+++ b/daemon/core/logger-factory.cpp
@@ -0,0 +1,178 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include "logger-factory.hpp"
+
+namespace nfd {
+
+LoggerFactory&
+LoggerFactory::getInstance()
+{
+  static LoggerFactory globalLoggerFactory;
+
+  return globalLoggerFactory;
+}
+
+LoggerFactory::LoggerFactory()
+  : m_defaultLevel(LOG_INFO)
+{
+  m_levelNames["NONE"] = LOG_NONE;
+  m_levelNames["ERROR"] = LOG_ERROR;
+  m_levelNames["WARN"] = LOG_WARN;
+  m_levelNames["INFO"] = LOG_INFO;
+  m_levelNames["DEBUG"] = LOG_DEBUG;
+  m_levelNames["TRACE"] = LOG_TRACE;
+  m_levelNames["ALL"] = LOG_ALL;
+}
+
+void
+LoggerFactory::setConfigFile(ConfigFile& config)
+{
+  config.addSectionHandler("log", bind(&LoggerFactory::onConfig, this, _1, _2, _3));
+}
+
+LogLevel
+LoggerFactory::parseLevel(const std::string& level)
+{
+  std::string upperLevel = level;
+  boost::to_upper(upperLevel);
+
+  // std::cerr << "parsing level: " << upperLevel << std::endl;;
+  // std::cerr << "# levels: " << m_levelNames.size() << std::endl;
+  // std::cerr << m_levelNames.begin()->first << std::endl;
+
+  LevelMap::const_iterator levelIt = m_levelNames.find(upperLevel);
+  if (levelIt != m_levelNames.end())
+    {
+      return levelIt->second;
+    }
+  try
+    {
+      uint32_t levelNo = boost::lexical_cast<uint32_t>(level);
+
+      if ((LOG_NONE <= levelNo && levelNo <= LOG_TRACE) ||
+          levelNo == LOG_ALL)
+        {
+          return static_cast<LogLevel>(levelNo);
+        }
+    }
+  catch (const boost::bad_lexical_cast& error)
+    {
+    }
+  throw LoggerFactory::Error("Unsupported logging level \"" +
+                             level + "\"");
+}
+
+
+void
+LoggerFactory::onConfig(const ConfigSection& section,
+                        bool isDryRun,
+                        const std::string& filename)
+{
+// log
+// {
+//   ; default_level specifies the logging level for modules
+//   ; that are not explicitly named. All debugging levels
+//   ; listed above the selected value are enabled.
+//
+//   default_level INFO
+//
+//   ; You may also override the default for specific modules:
+//
+//   FibManager DEBUG
+//   Forwarder WARN
+// }
+
+  // std::cerr << "loading logging configuration" << std::endl;
+  for (ConfigSection::const_iterator item = section.begin();
+       item != section.end();
+       ++item)
+    {
+      std::string levelString;
+      try
+        {
+          levelString = item->second.get_value<std::string>();
+        }
+      catch (const boost::property_tree::ptree_error& error)
+        {
+        }
+
+      if (levelString.empty())
+        {
+          throw LoggerFactory::Error("No logging level found for option \"" + item->first + "\"");
+        }
+
+      LogLevel level = parseLevel(levelString);
+
+      if (item->first == "default_level")
+        {
+          if (!isDryRun)
+            {
+              setDefaultLevel(level);
+            }
+        }
+      else
+        {
+          LoggerMap::iterator loggerIt = m_loggers.find(item->first);
+          if (loggerIt == m_loggers.end())
+            {
+              throw LoggerFactory::Error("Invalid module name \"" +
+                                         item->first + "\" in configuration file");
+            }
+
+          if (!isDryRun)
+            {
+              // std::cerr << "changing level for module " << item->first << " to " << level << std::endl;
+              loggerIt->second.setLogLevel(level);
+            }
+        }
+    }
+}
+
+void
+LoggerFactory::setDefaultLevel(LogLevel level)
+{
+  // std::cerr << "changing to default_level " << level << std::endl;
+
+  m_defaultLevel = level;
+  for (LoggerMap::iterator i = m_loggers.begin(); i != m_loggers.end(); ++i)
+    {
+      // std::cerr << "changing " << i->first << " to default " << m_defaultLevel << std::endl;
+      i->second.setLogLevel(m_defaultLevel);
+    }
+}
+
+Logger&
+LoggerFactory::create(const std::string& moduleName)
+{
+  return LoggerFactory::getInstance().createLogger(moduleName);
+}
+
+Logger&
+LoggerFactory::createLogger(const std::string& moduleName)
+{
+  // std::cerr << "creating logger for " << moduleName
+  //           << " with level " << m_defaultLevel << std::endl;
+
+  std::pair<LoggerMap::iterator, bool> loggerIt =
+    m_loggers.insert(NameAndLogger(moduleName, Logger(moduleName, m_defaultLevel)));
+
+  return loggerIt.first->second;
+}
+
+std::list<std::string>
+LoggerFactory::getModules() const
+{
+  std::list<std::string> modules;
+  for (LoggerMap::const_iterator i = m_loggers.begin(); i != m_loggers.end(); ++i)
+    {
+      modules.push_back(i->first);
+    }
+
+  return modules;
+}
+
+} // namespace nfd
diff --git a/daemon/core/logger-factory.hpp b/daemon/core/logger-factory.hpp
new file mode 100644
index 0000000..52a05f4
--- /dev/null
+++ b/daemon/core/logger-factory.hpp
@@ -0,0 +1,89 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
+/*
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_CORE_LOGGER_FACTORY_HPP
+#define NFD_CORE_LOGGER_FACTORY_HPP
+
+#include "common.hpp"
+#include "mgmt/config-file.hpp"
+#include "logger.hpp"
+
+namespace nfd {
+
+class LoggerFactory : noncopyable
+{
+public:
+
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& error)
+      : std::runtime_error(error)
+    {
+    }
+  };
+
+  static LoggerFactory&
+  getInstance();
+
+  void
+  setConfigFile(ConfigFile& config);
+
+  void
+  onConfig(const ConfigSection& section, bool isDryRun, const std::string& filename);
+
+  std::list<std::string>
+  getModules() const;
+
+  static Logger&
+  create(const std::string& moduleName);
+
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+
+  // these methods are used during unit-testing
+
+  LogLevel
+  getDefaultLevel() const;
+
+  void
+  setDefaultLevel(LogLevel level);
+
+private:
+
+  LoggerFactory();
+
+  Logger&
+  createLogger(const std::string& moduleName);
+
+  LogLevel
+  parseLevel(const std::string& level);
+
+private:
+
+  typedef std::map<std::string, LogLevel> LevelMap;
+  typedef std::pair<std::string, LogLevel> NameAndLevel;
+
+  LevelMap m_levelNames;
+
+  typedef std::map<std::string, Logger> LoggerMap;
+  typedef std::pair<std::string, Logger> NameAndLogger;
+
+  LoggerMap m_loggers;
+
+  LogLevel m_defaultLevel;
+};
+
+inline LogLevel
+LoggerFactory::getDefaultLevel() const
+{
+  return m_defaultLevel;
+}
+
+} // namespace nfd
+
+#endif // NFD_CORE_LOGGER_FACTORY_HPP
diff --git a/daemon/core/logger.cpp b/daemon/core/logger.cpp
deleted file mode 100644
index 928b112..0000000
--- a/daemon/core/logger.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
-/*
- * Copyright (C) 2014 Named Data Networking Project
- * See COPYING for copyright and distribution information.
- *
- * Author: Ilya Moiseenko <iliamo@ucla.edu>
- */
-
-#include "logger.hpp"
-#include <boost/algorithm/string.hpp>
-
-namespace nfd {
-
-inline static bool
-isNumber(const std::string& s)
-{
-  if (s.empty())
-    return false;
-
-  for (std::string::const_iterator i = s.begin(); i != s.end(); ++i)
-    {
-      if (!std::isdigit(*i))
-        return false;
-    }
-
-  return true;
-}
-
-Logger::Logger(const std::string& name)
-  : m_moduleName(name)
-{
-  char* nfdLog = getenv("NFD_LOG");
-  if (nfdLog)
-    {
-      std::string level = nfdLog;
-      if (isNumber(nfdLog))
-        {
-          setLogLevel(boost::lexical_cast<uint32_t>(nfdLog));
-        }
-      else if (boost::iequals(level, "none"))
-        {
-          m_enabledLogLevel = LOG_NONE;
-        }
-      else if (boost::iequals(level, "error"))
-        {
-          m_enabledLogLevel = LOG_ERROR;
-        }
-      else if (boost::iequals(level, "warn"))
-        {
-          m_enabledLogLevel = LOG_WARN;
-        }
-      else if (boost::iequals(level, "info"))
-        {
-          m_enabledLogLevel = LOG_INFO;
-        }
-      else if (boost::iequals(level, "debug"))
-        {
-          m_enabledLogLevel = LOG_DEBUG;
-        }
-      else if (boost::iequals(level, "trace"))
-        {
-          m_enabledLogLevel = LOG_TRACE;
-        }
-      else
-        {
-          m_enabledLogLevel = LOG_ALL;
-        }
-    }
-  else
-    {
-      m_enabledLogLevel = LOG_WARN;
-    }
-}
-
-std::ostream&
-operator<<(std::ostream& output, const Logger& obj)
-{
-  output << obj.getName();
-  return output;
-}
-
-} // namespace nfd
diff --git a/daemon/core/logger.hpp b/daemon/core/logger.hpp
index aad2394..604c304 100644
--- a/daemon/core/logger.hpp
+++ b/daemon/core/logger.hpp
@@ -10,7 +10,6 @@
 #define NFD_CORE_LOGGER_HPP
 
 #include "common.hpp"
-#include <iostream>
 
 namespace nfd {
 
@@ -28,81 +27,103 @@
 class Logger
 {
 public:
-  explicit
-  Logger(const std::string& name);
-  
-  bool
-  isEnabled(LogLevel level)
+
+  Logger(const std::string& name, LogLevel level)
+    : m_moduleName(name)
+    , m_enabledLogLevel(level)
   {
+  }
+
+  bool
+  isEnabled(LogLevel level) const
+  {
+    // std::cerr << m_moduleName <<
+    //   " enabled = " << m_enabledLogLevel
+    //           << " level = " << level << std::endl;
     return (m_enabledLogLevel >= level);
   }
 
   void
-  setLogLevel(uint32_t level)
+  setLogLevel(LogLevel level)
   {
-    m_enabledLogLevel = static_cast<LogLevel>(level);
+    m_enabledLogLevel = level;
   }
-  
+
   const std::string&
   getName() const
   {
     return m_moduleName;
   }
-  
+
+  void
+  setName(const std::string& name)
+  {
+    m_moduleName = name;
+  }
+
 private:
   std::string m_moduleName;
-  uint32_t    m_enabledLogLevel;
+  LogLevel    m_enabledLogLevel;
 };
 
-std::ostream&
-operator<<(std::ostream& output, const Logger& obj);
+inline std::ostream&
+operator<<(std::ostream& output, const Logger& obj)
+{
+  output << obj.getName();
+  return output;
+}
+
+} // namespace nfd
+
+#include "core/logger-factory.hpp"
+
+namespace nfd {
 
 #define NFD_LOG_INIT(name) \
-  static nfd::Logger \
-  g_logger = nfd::Logger(name);
+static nfd::Logger& g_logger = nfd::LoggerFactory::create(name);
 
 #define NFD_LOG_INCLASS_DECLARE()        \
-  static nfd::Logger g_logger;
+static nfd::Logger& g_logger;
 
 #define NFD_LOG_INCLASS_DEFINE(cls, name)        \
-  nfd::Logger cls::g_logger = nfd::Logger(name);
+nfd::Logger& cls::g_logger = nfd::LoggerFactory::create(name);
 
 #define NFD_LOG_INCLASS_TEMPLATE_DEFINE(cls, name)   \
-  template<class T> \
-  nfd::Logger cls<T>::g_logger = nfd::Logger(name);
+template<class T>                                                       \
+nfd::Logger& cls<T>::g_logger = nfd::LoggerFactory::create(name);
 
-#define NFD_LOG_INCLASS_TEMPLATE_SPECIALIZATION_DEFINE(cls, specialization, name)    \
-  template<> \
-  nfd::Logger cls<specialization>::g_logger = nfd::Logger(name);
+#define NFD_LOG_INCLASS_TEMPLATE_SPECIALIZATION_DEFINE(cls, specialization, name) \
+template<> \
+nfd::Logger& cls<specialization>::g_logger = nfd::LoggerFactory::create(name);
 
 #define NFD_LOG_INCLASS_2TEMPLATE_SPECIALIZATION_DEFINE(cls, s1, s2, name) \
-  template<> \
-  nfd::Logger cls<s1, s2>::g_logger = nfd::Logger(name);
+template<>                                                              \
+nfd::Logger& cls<s1, s2>::g_logger = nfd::LoggerFactory::create(name);
+
 
 #define NFD_LOG_TRACE(expression) \
-    if(g_logger.isEnabled(nfd::LOG_TRACE)) \
-       std::cerr<<"TRACE: "<<"["<<g_logger<<"] " << expression << "\n"
+if (g_logger.isEnabled(nfd::LOG_TRACE))                                 \
+  std::cerr<<"TRACE: "<<"["<<g_logger<<"] " << expression << "\n"
 
 #define NFD_LOG_DEBUG(expression)\
-    if(g_logger.isEnabled(nfd::LOG_DEBUG)) \
-       std::cerr<<"DEBUG: "<<"["<<g_logger<<"] " << expression <<"\n"
+if (g_logger.isEnabled(nfd::LOG_DEBUG))                                 \
+  std::cerr<<"DEBUG: "<<"["<<g_logger<<"] " << expression <<"\n"
 
-#define NFD_LOG_WARN(expression) \
-    if(g_logger.isEnabled(nfd::LOG_WARN)) \
-       std::cerr<<"WARNING: "<<"["<<g_logger<<"] " << expression <<"\n"
+#define NFD_LOG_WARN(expression)                                        \
+if (g_logger.isEnabled(nfd::LOG_WARN))                                  \
+  std::cerr<<"WARNING: "<<"["<<g_logger<<"] " << expression <<"\n"
 
 #define NFD_LOG_INFO(expression)\
-    if(g_logger.isEnabled(nfd::LOG_INFO)) \
-       std::cerr<<"INFO: "<<"["<<g_logger<<"] " << expression <<"\n"
-  
-#define NFD_LOG_ERROR(expression)\
-    if(g_logger.isEnabled(nfd::LOG_ERROR)) \
-       std::cerr<<"ERROR: "<<"["<<g_logger<<"] " << expression <<"\n"
-  
-#define NFD_LOG_FATAL(expression)\
-    std::cerr<<"FATAL: "<<"["<<g_logger<<"] " << expression <<"\n"
-  
-} //namespace nfd
+if (g_logger.isEnabled(nfd::LOG_INFO))                          \
+  std::cerr<<"INFO: "<<"["<<g_logger<<"] " << expression <<"\n"
 
+#define NFD_LOG_ERROR(expression)                                       \
+if (g_logger.isEnabled(nfd::LOG_ERROR))                                  \
+  std::cerr<<"ERROR: "<<"["<<g_logger<<"] " << expression <<"\n"
+
+#define NFD_LOG_FATAL(expression)\
+std::cerr<<"FATAL: "<<"["<<g_logger<<"] " << expression <<"\n"
+
+} //namespace nfd
 
 #endif
diff --git a/daemon/core/network-interface.cpp b/daemon/core/network-interface.cpp
index 65a690c..7319e97 100644
--- a/daemon/core/network-interface.cpp
+++ b/daemon/core/network-interface.cpp
@@ -5,6 +5,7 @@
  */
 
 #include "network-interface.hpp"
+#include "core/logger.hpp"
 
 #include <boost/foreach.hpp>