Browse Source

[trac555] Checkpoint

Added basic logger manager and its implementation, plus an untested
console appender.  Also modified logger_support_test to use that
appender and have got part way through creating a file logger test.
Stephen Morris 14 years ago
parent
commit
cfa31a9924

+ 1 - 0
src/lib/log/Makefile.am

@@ -15,6 +15,7 @@ liblog_la_SOURCES += logger_level.h
 liblog_la_SOURCES += logger_level.h
 liblog_la_SOURCES += logger_level_impl.cc logger_level_impl.h
 liblog_la_SOURCES += logger_manager.cc logger_manager.h
+liblog_la_SOURCES += logger_manager_impl.cc logger_manager_impl.h
 liblog_la_SOURCES += logger_specification.h
 liblog_la_SOURCES += logger_support.cc logger_support.h
 liblog_la_SOURCES += macros.h

+ 1 - 1
src/lib/log/logger_impl.cc

@@ -33,7 +33,7 @@
 
 #include <util/strutil.h>
 
-// Note: as log4cplus and th3e BIND 10 logger have many concepts in common, and
+// Note: as log4cplus and the BIND 10 logger have many concepts in common, and
 // thus many similar names, to disambiguate types we don't "use" the log4cplus
 // namespace: instead, all log4cplus types are explicitly qualified.
 

+ 0 - 2
src/lib/log/logger_manager.cc

@@ -18,9 +18,7 @@
 namespace isc {
 namespace log {
 
-void LoggerManagerImpl::processInit() {}
 void LoggerManagerImpl::processEnd() {}
-void LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {}
 
 // Constructor - create the implementation  class.
 LoggerManager::LoggerManager() {

+ 18 - 0
src/lib/log/logger_manager.h

@@ -56,6 +56,24 @@ public:
         processEnd();
     }
 
+    /// \brief Process a single specification
+    ///
+    /// A convenience function for a single specification.
+    ///
+    /// \param spec Specification to process
+    void process(const LoggerSpecification& spec) {
+        processInit();
+        processSpecification(spec);
+        processEnd();
+    }
+
+    /// \brief Initialization
+    ///
+    /// Static method for initializing the whole of the logging system.  This
+    /// must be called before anything else.
+    static void init();
+
+
 private:
     /// \brief Initialize Processing
     ///

+ 108 - 0
src/lib/log/logger_manager_impl.cc

@@ -0,0 +1,108 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log4cplus/logger.h>
+#include <log4cplus/configurator.h>
+#include <log4cplus/consoleappender.h>
+
+#include "log/logger_level_impl.h"
+#include "log/logger_manager_impl.h"
+#include "log/logger_specification.h"
+#include "log/root_logger_name.h"
+
+#include "exceptions/exceptions.h"
+
+// Generated exceptions.  Methods in this file can't log exceptions as they may
+// occur when logging is disabled or in an inconsistent state.
+class UnknownLoggingDestination : public isc::Exception {
+public:
+    UnknownLoggingDestination(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
+};
+
+// Reset hierarchy of loggers back to default settings.  This removes all
+// appenders from loggers, sets their severity to NOT_SET (so that events are
+// passed back to the parent) and resets the root logger to logging
+// informational messages.  (This last is not a log4cplus default, so we have to
+// explicitly reset the logging severity.)
+
+namespace isc {
+namespace log {
+
+// Reset hierarchy back to default.  Note that this does not delete existing
+// loggers, it makes them inactive.  (So a logger is never removed, even if a
+// configuration update removes the logger.)
+
+void
+LoggerManagerImpl::processInit() {
+    log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
+    log4cplus::Logger::getRoot().setLogLevel(log4cplus::INFO_LOG_LEVEL);
+}
+
+// Process logging specification.  Set up the common states then dispatch to
+// add output specifications.
+
+void
+LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
+
+    // Get/construct the logger for which this specification applies.
+    log4cplus::Logger logger = (spec.getName() == getRootLoggerName()) ?
+                               log4cplus::Logger::getRoot() :
+                               log4cplus::Logger::getInstance(spec.getName());
+
+    // Set severity level according to specification entry.
+    logger.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
+                       Level(spec.getSeverity(), spec.getDbglevel())));
+
+    // Set the additive flag.
+    logger.setAdditivity(spec.getAdditive());
+
+    // Now process output specifications.
+    for (LoggerSpecification::const_iterator i = spec.begin();
+         i != spec.end(); ++i) {
+        switch (i->destination) {
+        case OutputOption::DEST_CONSOLE:
+            createConsoleAppender(logger, *i);
+            break;
+
+        case OutputOption::DEST_FILE:
+            createFileAppender(logger, *i);
+            break;
+
+        case OutputOption::DEST_SYSLOG:
+            createSyslogAppender(logger, *i);
+            break;
+
+        default:
+            isc_throw(UnknownLoggingDestination,
+                      "Unknown logging destination, code = " << i->destination);
+        }
+    }
+}
+
+// Console appender - log to either stdout or stderr.
+void
+LoggerManagerImpl::createConsoleAppender(log4cplus::Logger& logger,
+                                         const OutputOption& opt)
+{
+    log4cplus::SharedAppenderPtr console(
+        new log4cplus::ConsoleAppender(
+            (opt.stream == OutputOption::STR_STDERR), opt.flush));
+    logger.addAppender(console);
+}
+
+
+} // namespace log
+} // namespace isc

+ 37 - 0
src/lib/log/logger_manager_impl.h

@@ -17,6 +17,11 @@
 
 #include <log/logger_specification.h>
 
+// Forward declaration to avoid need to include log4cplus header file here.
+namespace log4cplus {
+class Logger;
+}
+
 namespace isc {
 namespace log {
 
@@ -54,6 +59,38 @@ public:
     ///
     /// Terminates the processing of the logging specifications.
     void processEnd();
+
+private:
+    /// \brief Create console appender
+    ///
+    /// Creates an object that, when attached to a logger, will log to one
+    /// of the output streams (stdout or stderr).
+    ///
+    /// \param logger Log4cplus logger to which the appender must be attached.
+    /// \param opt Output options for this appender.
+    void createConsoleAppender(log4cplus::Logger& logger,
+                               const OutputOption& opt);
+
+    /// \brief Create file appender
+    ///
+    /// Creates an object that, when attached to a logger, will log to a
+    /// specified file.  This also includes the ability to "roll" files when
+    /// they reach a specified size.
+    ///
+    /// \param logger Log4cplus logger to which the appender must be attached.
+    /// \param opt Output options for this appender.
+    void createFileAppender(log4cplus::Logger& logger,
+                            const OutputOption& opt) {}
+
+    /// \brief Create syslog appender
+    ///
+    /// Creates an object that, when attached to a logger, will log to the
+    /// syslog file.
+    ///
+    /// \param logger Log4cplus logger to which the appender must be attached.
+    /// \param opt Output options for this appender.
+    void createSyslogAppender(log4cplus::Logger& logger,
+                              const OutputOption& opt) {}
 };
 
 } // namespace log

+ 19 - 19
src/lib/log/logger_specification.h

@@ -53,33 +53,33 @@ public:
         additive_(additive)
     {}
 
-    /// \brief Set the name
+    /// \brief Set the name of the logger.
     ///
-    /// \param name Name of the logger .
+    /// \param name Name of the logger.
     void setName(const std::string& name) {
         name_ = name;
     }
 
-    /// \return Return logger name
+    /// \return Return logger name.
     std::string getName() const {
         return name_;
     }
 
-    /// \brief Set the severity
+    /// \brief Set the severity.
     ///
-    /// \param severity New severity of the logger .
+    /// \param severity New severity of the logger.
     void setSeverity(isc::log::Severity severity) {
         severity_ = severity;
     }
 
-    /// \return Return logger severity
+    /// \return Return logger severity.
     isc::log::Severity getSeverity() const {
         return severity_;
     }
 
-    /// \brief Set the debug level
+    /// \brief Set the debug level.
     ///
-    /// \param dbglevel New debug level of the logger .
+    /// \param dbglevel New debug level of the logger.
     void setDbglevel(int dbglevel) {
         dbglevel_ = dbglevel;
     }
@@ -89,51 +89,51 @@ public:
         return dbglevel_;
     }
 
-    /// \brief Set the additive flag
+    /// \brief Set the additive flag.
     ///
-    /// \param additive New value of the additive flag
+    /// \param additive New value of the additive flag.
     void setAdditive(bool additive) {
         additive_ = additive;
     }
 
-    /// \return Return additive flag
+    /// \return Return additive flag.
     int getAdditive() const {
         return additive_;
     }
 
-    /// \brief Add output option
+    /// \brief Add output option.
     ///
-    /// \param Option to add to the list
+    /// \param Option to add to the list.
     void addOutputOption(const OutputOption& option) {
         options_.push_back(option);
     }
 
-    /// \return Iterator to start of output options
+    /// \return Iterator to start of output options.
     iterator begin() {
         return options_.begin();
     }
 
-    /// \return Iterator to start of output options
+    /// \return Iterator to start of output options.
     const_iterator begin() const {
         return options_.begin();
     }
 
-    /// \return Iterator to end of output options
+    /// \return Iterator to end of output options.
     iterator end() {
         return options_.end();
     }
 
-    /// \return Iterator to end of output options
+    /// \return Iterator to end of output options.
     const_iterator end() const {
         return options_.end();
     }
 
-    /// \return Number of output specification options
+    /// \return Number of output specification options.
     size_t optionCount() const {
         return options_.size();
     }
 
-    /// \brief Reset back to defaults
+    /// \brief Reset back to defaults.
     void reset() {
         name_ = "";
         severity_ = isc::log::INFO;

+ 5 - 2
src/lib/log/logger_support.h

@@ -15,6 +15,8 @@
 #ifndef __LOGGER_SUPPORT_H
 #define __LOGGER_SUPPORT_H
 
+#include <unistd.h>
+
 #include <string>
 #include <log/logger.h>
 
@@ -36,8 +38,9 @@ namespace log {
 /// \param severity Severity at which to log
 /// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
 /// \param file Name of the local message file.
-void initLogger(const std::string& root, isc::log::Severity severity,
-    int dbglevel, const char* file);
+void initLogger(const std::string& root,
+                isc::log::Severity severity = isc::log::INFO,
+                int dbglevel = 0, const char* file = NULL);
 
 
 /// \brief Run-Time Initialization from Environment

+ 1 - 0
src/lib/log/tests/Makefile.am

@@ -33,6 +33,7 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CPLUS_LDFLAGS)
 run_unittests_LDADD  = $(GTEST_LDADD)
 run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 
 TESTS += logger_support_test

+ 53 - 4
src/lib/log/tests/logger_manager_unittest.cc

@@ -21,6 +21,10 @@
 
 #include <gtest/gtest.h>
 
+#include <boost/scoped_array.hpp>
+
+#include <exceptions/exceptions.h>
+
 #include <log/macros.h>
 #include <log/messagedef.h>
 #include <log/logger.h>
@@ -29,6 +33,7 @@
 #include <log/logger_specification.h>
 #include <log/output_option.h>
 
+using namespace isc;
 using namespace isc::log;
 using namespace std;
 
@@ -41,8 +46,6 @@ class DerivedLogger : public isc::log::Logger {
 public:
     DerivedLogger(std::string name) : isc::log::Logger(name)
     {}
-    virtual ~DerivedLogger()
-    {}
 
     static void reset() {
         isc::log::Logger::reset();
@@ -70,10 +73,14 @@ public:
 
     // Constructor - allocate file and create the specification object
     SpecificationForFileLogger() : spec_(), name_(""), logname_("filelogger") {
+
+        // Set the output to a temporary file.
         OutputOption option;
         option.destination = OutputOption::DEST_FILE;
-        name_ = option.filename = std::string(tmpnam(NULL));
+        option.filename = name_ = createTempFilename();
 
+        // Set target output to the file logger.  The defauls indicate
+        // INFO severity.
         spec_.setName(logname_);
         spec_.addOutputOption(option);
     }
@@ -100,6 +107,48 @@ public:
         return name_;
     }
 
+    // Create temporary filename
+    //
+    // The compiler warns against tmpnam() and suggests mkstemp instead.
+    // Unfortunately, this creates the filename and opens it.  So we need to
+    // close and delete the file before returning the name.  Also, the name
+    // is based on the template supplied and the name of the temporary
+    // directory may vary between systems.  So translate TMPDIR and if that
+    // does not exist, use /tmp.
+    //
+    // \return Temporary file name
+    std::string createTempFilename() {
+
+        // Get prefix.  Note that in all copies, strncpy does not guarantee
+        // a null-terminated string, hence the explict setting of the last
+        // character to NULL.
+        ostringstream filename;
+
+        if (getenv("TMPDIR") != NULL) {
+            filename << getenv("TMPDIR");
+        } else {
+            filename << "/tmp";
+        }
+        filename << "/bind10_logger_manager_test_XXXXXX";
+
+        cout << "*** file name before call is " << filename.str() << "\n";
+
+        // Copy into writeable storage for the call to mkstemp
+        boost::scoped_array<char> tname(new char[filename.str().size() + 1]);
+        strcpy(tname.get(), filename.str().c_str());
+
+        // Create file, close and delete it, and store the name for later.
+        int filenum = mkstemp(tname.get());
+        cout << "*** file name after call is " << tname.get() << "\n";
+        if (filenum == -1) {
+            isc_throw(Exception, "Unable to obtain unique filename");
+        }
+        close(filenum);
+        unlink(tname.get());
+
+        return (string(tname.get()));
+    }
+
 
 private:
     LoggerSpecification     spec_;      // Specification for this file logger
@@ -153,7 +202,7 @@ void checkFileContents(const std::string& filename, T start, T finish) {
 }
 
 // Check that the logger correctly creates something logging to a file.
-TEST_F(LoggerManagerTest, DISABLED_FileLogger) {
+TEST_F(LoggerManagerTest, FileLogger) {
 
     // Create a specification for the file logger and use the manager to
     // connect the "filelogger" logger to it.

+ 113 - 43
src/lib/log/tests/logger_support_test.cc

@@ -20,68 +20,124 @@
 #include <unistd.h>
 #include <string.h>
 
+#include <boost/lexical_cast.hpp>
+
 #include <iostream>
+#include <string>
 
 #include <log/logger.h>
-#include <log/macros.h>
+#include <log/logger_manager.h>
+#include <log/logger_specification.h>
 #include <log/logger_support.h>
+#include <log/macros.h>
 #include <log/root_logger_name.h>
 
 // Include a set of message definitions.
 #include <log/messagedef.h>
 
 using namespace isc::log;
+using namespace std;
+
+
+// Print usage information
+
+void usage() {
+    cout <<
+"logger_support_test [-h] [-s severity] [-d dbglevel] [-c stream] [localfile]\n"
+"\n"
+"   -h              Print this message and exit\n"
+"   -s severity     Set the severity of messages output.  'severity' is one\n"
+"                   of 'debug', 'info', 'warn', 'error', 'fatal', the default\n"
+"                   being 'info'.\n"
+"   -d dbglevel     Debug level.  Only interpreted if the severity is 'debug'\n"
+"                   this is a number between 0 and 99.\n"
+"   -c stream       Send output to the console.  'stream' is one of 'stdout'\n"
+"                   of 'stderr'.  The '-c' switch is incompatible with '-f'\n"
+"                   and '-l'\n"
+"\n"
+"If none of -c, -f or -l is given, by default, output is sent to stdout\n";
+}
 
-// Declare logger to use an example.
-Logger logger_ex("example");
 
-// The program is invoked:
-//
-// logger_support_test [-s severity] [-d level ] [local_file]
-//
-// "severity" is one of "debug", "info", "warn", "error", "fatal"
-// "level" is the debug level, a number between 0 and 99
-// "local_file" is the name of a local file.
-//
 // The program sets the attributes on the root logger and logs a set of
 // messages.  Looking at the output determines whether the program worked.
 
 int main(int argc, char** argv) {
+    const string ROOT_NAME = "alpha";
 
-    isc::log::Severity  severity = isc::log::INFO;  // Default logger severity
-    int                 dbglevel = -1;              // Logger debug level
-    const char*         localfile = NULL;           // Local message file
-    int                 option;                     // For getopt() processing
-    Logger              logger_dlm("dlm");          // Another example logger
+    bool                c_found = false;    // Set true if "-c" found
+    bool                f_found = false;    // Set true if "-f" found
+    bool                l_found = false;    // Set true if "-l" found
+
+    const char*         localfile = NULL;   // Local message file
+    int                 option;             // For getopt() processing
+
+    LoggerSpecification spec(ROOT_NAME);    // Logger specification
+    OutputOption        outopt;             // Logger output option
+
+    // Initialize loggers (to set the root name and initialize logging);
+    // We'll reset them later.
+    setRootLoggerName(ROOT_NAME);
+    Logger rootLogger(ROOT_NAME);
 
     // Parse options
-    while ((option = getopt(argc, argv, "s:d:")) != -1) {
+    while ((option = getopt(argc, argv, "hc:d:s:")) != -1) {
         switch (option) {
-            case 's':
-                if (strcmp(optarg, "debug") == 0) {
-                    severity = isc::log::DEBUG;
-                } else if (strcmp(optarg, "info") == 0) {
-                    severity = isc::log::INFO;
-                } else if (strcmp(optarg, "warn") == 0) {
-                    severity = isc::log::WARN;
-                } else if (strcmp(optarg, "error") == 0) {
-                    severity = isc::log::ERROR;
-                } else if (strcmp(optarg, "fatal") == 0) {
-                    severity = isc::log::FATAL;
-                } else {
-                    std::cout << "Unrecognised severity option: " <<
-                        optarg << "\n";
-                    exit(1);
-                }
-                break;
-
-            case 'd':
-                dbglevel = atoi(optarg);
-                break;
-
-            default:
-                std::cout << "Unrecognised option: " <<
-                    static_cast<char>(option) << "\n";
+        case 'c':
+            if (f_found || l_found) {
+                cerr << "Cannot specify -c with -f or -l\n";
+                return (1);
+            }
+
+            c_found = true;
+            outopt.destination = OutputOption::DEST_CONSOLE;
+
+            if (strcmp(optarg, "stdout") == 0) {
+                outopt.stream = OutputOption::STR_STDOUT;
+
+            } else if (strcmp(optarg, "stderr") == 0) {
+                outopt.stream = OutputOption::STR_STDERR;
+
+            } else {
+                cerr << "Unrecognised console option: " << optarg << "\n";
+                return (1);
+            }
+            break;
+
+        case 'd':
+            spec.setDbglevel(boost::lexical_cast<int>(optarg));
+            break;
+
+        case 'h':
+            usage();
+            return (0);
+
+        case 's':
+            if (strcmp(optarg, "debug") == 0) {
+                spec.setSeverity(isc::log::DEBUG);
+
+            } else if (strcmp(optarg, "info") == 0) {
+                spec.setSeverity(isc::log::INFO);
+
+            } else if (strcmp(optarg, "warn") == 0) {
+                spec.setSeverity(isc::log::WARN);
+
+            } else if (strcmp(optarg, "error") == 0) {
+                spec.setSeverity(isc::log::ERROR);
+
+            } else if (strcmp(optarg, "fatal") == 0) {
+                spec.setSeverity(isc::log::FATAL);
+
+            } else {
+                cerr << "Unrecognised severity option: " << optarg << "\n";
+                return (1);
+            }
+            break;
+
+        default:
+            std::cerr << "Unrecognised option: " <<
+                static_cast<char>(option) << "\n";
+            return (1);
         }
     }
 
@@ -90,9 +146,23 @@ int main(int argc, char** argv) {
     }
 
     // Update the logging parameters
-    initLogger("alpha", severity, dbglevel, localfile);
+    initLogger(ROOT_NAME, isc::log::INFO, 0, localfile);
+
+    // Set an output option if we have not done so already.
+    if (! (c_found || f_found || l_found)) {
+        outopt.destination = OutputOption::DEST_CONSOLE;
+        outopt.stream = OutputOption::STR_STDOUT;
+    }
+    spec.addOutputOption(outopt);
+
+    // Set the logging options for the root logger.
+    LoggerManager manager;
+    manager.process(spec);
+
 
     // Log a few messages
+    isc::log::Logger logger_dlm("dlm");
+    isc::log::Logger logger_ex("example");
     LOG_FATAL(logger_ex, MSG_WRITERR).arg("test1").arg("42");
     LOG_ERROR(logger_ex, MSG_RDLOCMES).arg("dummy/file");
     LOG_WARN(logger_dlm, MSG_READERR).arg("a.txt").arg("dummy reason");