Parcourir la source

[master] Merge branch 'trac2445'

Jelte Jansen il y a 12 ans
Parent
commit
74a0abe5a6

+ 1 - 0
configure.ac

@@ -1376,6 +1376,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/log/tests/console_test.sh
            src/lib/log/tests/destination_test.sh
            src/lib/log/tests/init_logger_test.sh
+           src/lib/log/tests/buffer_logger_test.sh
            src/lib/log/tests/local_file_test.sh
            src/lib/log/tests/logger_lock_test.sh
            src/lib/log/tests/severity_test.sh

+ 4 - 2
src/bin/auth/main.cc

@@ -147,7 +147,7 @@ main(int argc, char* argv[]) {
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(AUTH_NAME,
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, true);
 
     int ret = 0;
 
@@ -256,7 +256,9 @@ main(int argc, char* argv[]) {
 
     // If we haven't registered callback for data sources, this will be just
     // no-op.
-    config_session->removeRemoteConfig("data_sources");
+    if (config_session != NULL) {
+        config_session->removeRemoteConfig("data_sources");
+    }
 
     delete xfrin_session;
     delete config_session;

+ 14 - 14
src/bin/bind10/bind10_src.py.in

@@ -48,7 +48,7 @@ else:
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"
     SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-    
+
 import subprocess
 import signal
 import re
@@ -76,7 +76,7 @@ import isc.bind10.socket_cache
 import libutil_io_python
 import tempfile
 
-isc.log.init("b10-boss")
+isc.log.init("b10-boss", buffer=True)
 logger = isc.log.Logger("boss")
 
 # Pending system-wide debug level definitions, the ones we
@@ -166,14 +166,14 @@ class ProcessStartError(Exception): pass
 
 class BoB:
     """Boss of BIND class."""
-    
+
     def __init__(self, msgq_socket_file=None, data_path=None,
                  config_filename=None, clear_config=False,
                  verbose=False, nokill=False, setuid=None, setgid=None,
                  username=None, cmdctl_port=None, wait_time=10):
         """
             Initialize the Boss of BIND. This is a singleton (only one can run).
-        
+
             The msgq_socket_file specifies the UNIX domain socket file that the
             msgq process listens on.  If verbose is True, then the boss reports
             what it is doing.
@@ -407,7 +407,7 @@ class BoB:
                     logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg)
             except:
                 logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg)
-        
+
         return False
 
     # The next few methods start the individual processes of BIND-10.  They
@@ -486,7 +486,7 @@ class BoB:
             time.sleep(1)
             time_remaining = time_remaining - 1
             msg, env = self.cc_session.group_recvmsg()
-        
+
         if not self.process_running(msg, "ConfigManager"):
             raise ProcessStartError("Configuration manager process has not started")
 
@@ -503,7 +503,7 @@ class BoB:
             process, the log_starting/log_started methods are not used.
         """
         logger.info(BIND10_STARTING_CC)
-        self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, 
+        self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
                                       self.config_handler,
                                       self.command_handler,
                                       socket_file = self.msgq_socket_file)
@@ -700,7 +700,7 @@ class BoB:
         except:
             pass
         # XXX: some delay probably useful... how much is uncertain
-        # I have changed the delay from 0.5 to 1, but sometime it's 
+        # I have changed the delay from 0.5 to 1, but sometime it's
         # still not enough.
         time.sleep(1)
         self.reap_children()
@@ -749,8 +749,8 @@ class BoB:
         return os.waitpid(-1, os.WNOHANG)
 
     def reap_children(self):
-        """Check to see if any of our child processes have exited, 
-        and note this for later handling. 
+        """Check to see if any of our child processes have exited,
+        and note this for later handling.
         """
         while True:
             try:
@@ -783,11 +783,11 @@ class BoB:
         """
             Restart any dead processes:
 
-            * Returns the time when the next process is ready to be restarted. 
+            * Returns the time when the next process is ready to be restarted.
             * If the server is shutting down, returns 0.
             * If there are no processes, returns None.
 
-            The values returned can be safely passed into select() as the 
+            The values returned can be safely passed into select() as the
             timeout value.
 
         """
@@ -1031,7 +1031,7 @@ boss_of_bind = None
 
 def reaper(signal_number, stack_frame):
     """A child process has died (SIGCHLD received)."""
-    # don't do anything... 
+    # don't do anything...
     # the Python signal handler has been set up to write
     # down a pipe, waking up our select() bit
     pass
@@ -1198,7 +1198,7 @@ and the created lock file must be writable for that user.
         except KeyError:
             pass
 
-        # Next try getting information about the user, assuming user name 
+        # Next try getting information about the user, assuming user name
         # passed.
         # If the information is both a valid user name and user number, we
         # prefer the name because we try it second. A minor point, hopefully.

+ 1 - 1
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -27,7 +27,7 @@ import glob
 import os.path
 import imp
 import isc.log
-isc.log.init("b10-cfgmgr")
+isc.log.init("b10-cfgmgr", buffer=True)
 from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError, logger
 from isc.log_messages.cfgmgr_messages import *
 

+ 1 - 1
src/bin/cmdctl/cmdctl.py.in

@@ -49,7 +49,7 @@ from hashlib import sha1
 from isc.util import socketserver_mixin
 from isc.log_messages.cmdctl_messages import *
 
-isc.log.init("b10-cmdctl")
+isc.log.init("b10-cmdctl", buffer=True)
 logger = isc.log.Logger("cmdctl")
 
 # Debug level for communication with BIND10

+ 1 - 1
src/bin/ddns/ddns.py.in

@@ -45,7 +45,7 @@ import os.path
 import signal
 import socket
 
-isc.log.init("b10-ddns")
+isc.log.init("b10-ddns", buffer=True)
 logger = isc.log.Logger("ddns")
 TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
 

+ 7 - 1
src/bin/dhcp4/main.cc

@@ -17,6 +17,7 @@
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <log/logger_support.h>
+#include <log/logger_manager.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -93,9 +94,10 @@ main(int argc, char* argv[]) {
     }
 
     // Initialize logging.  If verbose, we'll use maximum verbosity.
+    // If standalone is enabled, do not buffer initial log messages
     isc::log::initLogger(DHCP4_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
     LOG_INFO(dhcp4_logger, DHCP4_STARTING);
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
               .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -112,6 +114,10 @@ main(int argc, char* argv[]) {
                 LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
                 // Let's continue. It is useful to have the ability to run
                 // DHCP server in stand-alone mode, e.g. for testing
+                // We do need to make sure logging is no longer buffered
+                // since then it would not print until dhcp6 is stopped
+                isc::log::LoggerManager log_manager;
+                log_manager.process();
             }
         } else {
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);

+ 8 - 2
src/bin/dhcp6/main.cc

@@ -17,6 +17,7 @@
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <log/logger_support.h>
+#include <log/logger_manager.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -103,9 +104,10 @@ main(int argc, char* argv[]) {
     }
 
     // Initialize logging.  If verbose, we'll use maximum verbosity.
+    // If standalone is enabled, do not buffer initial log messages
     isc::log::initLogger(DHCP6_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
     LOG_INFO(dhcp6_logger, DHCP6_STARTING);
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
               .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -119,8 +121,12 @@ main(int argc, char* argv[]) {
                 server.establishSession();
             } catch (const std::exception& ex) {
                 LOG_ERROR(dhcp6_logger, DHCP6_SESSION_FAIL).arg(ex.what());
-                // Let's continue. It is useful to have the ability to run 
+                // Let's continue. It is useful to have the ability to run
                 // DHCP server in stand-alone mode, e.g. for testing
+                // We do need to make sure logging is no longer buffered
+                // since then it would not print until dhcp6 is stopped
+                isc::log::LoggerManager log_manager;
+                log_manager.process();
             }
         } else {
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);

+ 6 - 6
src/bin/resolver/main.cc

@@ -143,7 +143,7 @@ main(int argc, char* argv[]) {
     // temporary initLogger() code.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(RESOLVER_NAME,
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, true);
 
     // Print the starting message
     string cmdline = argv[0];
@@ -177,7 +177,7 @@ main(int argc, char* argv[]) {
 
         isc::cache::ResolverCache cache;
         resolver->setCache(cache);
-        
+
         // TODO priming query, remove root from direct
         // Fake a priming query result here (TODO2 how to flag non-expiry?)
         // propagation to runningquery. And check for forwarder mode?
@@ -185,21 +185,21 @@ main(int argc, char* argv[]) {
                                             isc::dns::Name("."),
                                             isc::dns::RRClass::IN(),
                                             isc::dns::RRType::NS()));
-        isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."), 
+        isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
                                          isc::dns::RRClass::IN(),
                                          isc::dns::RRType::NS(),
                                          isc::dns::RRTTL(8888)));
         root_ns_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::NS(),
                                                              isc::dns::RRClass::IN(),
                                                              "l.root-servers.net."));
-        isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"), 
+        isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
                                         isc::dns::RRClass::IN(),
                                         isc::dns::RRType::A(),
                                         isc::dns::RRTTL(8888)));
         root_a_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
                                                              isc::dns::RRClass::IN(),
                                                              "199.7.83.42"));
-        isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"), 
+        isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
                                         isc::dns::RRClass::IN(),
                                         isc::dns::RRType::AAAA(),
                                         isc::dns::RRTTL(8888)));
@@ -216,7 +216,7 @@ main(int argc, char* argv[]) {
         cache.update(root_ns_rrset);
         cache.update(root_a_rrset);
         cache.update(root_aaaa_rrset);
-        
+
         DNSService dns_service(io_service, checkin, lookup, answer);
         resolver->setDNSService(dns_service);
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);

+ 2 - 2
src/bin/stats/stats.py.in

@@ -31,7 +31,7 @@ import isc.util.process
 import isc.log
 from isc.log_messages.stats_messages import *
 
-isc.log.init("b10-stats")
+isc.log.init("b10-stats", buffer=True)
 logger = isc.log.Logger("stats")
 
 # Some constants for debug levels.
@@ -682,7 +682,7 @@ if __name__ == "__main__":
             help="enable maximum debug logging")
         (options, args) = parser.parse_args()
         if options.verbose:
-            isc.log.init("b10-stats", "DEBUG", 99)
+            isc.log.init("b10-stats", "DEBUG", 99, buffer=True)
         stats = Stats()
         stats.start()
     except OptionValueError as ove:

+ 2 - 2
src/bin/stats/stats_httpd.py.in

@@ -39,7 +39,7 @@ import isc.util.process
 import isc.log
 from isc.log_messages.stats_httpd_messages import *
 
-isc.log.init("b10-stats-httpd")
+isc.log.init("b10-stats-httpd", buffer=True)
 logger = isc.log.Logger("stats-httpd")
 
 # Some constants for debug levels.
@@ -609,7 +609,7 @@ if __name__ == "__main__":
             help="enable maximum debug logging")
         (options, args) = parser.parse_args()
         if options.verbose:
-            isc.log.init("b10-stats-httpd", "DEBUG", 99)
+            isc.log.init("b10-stats-httpd", "DEBUG", 99, buffer=True)
         stats_httpd = StatsHttpd()
         stats_httpd.start()
     except OptionValueError as ove:

+ 1 - 1
src/bin/xfrin/xfrin.py.in

@@ -36,7 +36,7 @@ from isc.xfrin.diff import Diff
 from isc.server_common.auth_command import auth_loadzone_command
 from isc.log_messages.xfrin_messages import *
 
-isc.log.init("b10-xfrin")
+isc.log.init("b10-xfrin", buffer=True)
 logger = isc.log.Logger("xfrin")
 
 # Pending system-wide debug level definitions, the ones we

+ 1 - 1
src/bin/xfrout/xfrout.py.in

@@ -38,7 +38,7 @@ import isc.server_common.tsig_keyring
 
 from isc.log_messages.xfrout_messages import *
 
-isc.log.init("b10-xfrout")
+isc.log.init("b10-xfrout", buffer=True)
 logger = isc.log.Logger("xfrout")
 
 # Pending system-wide debug level definitions, the ones we

+ 1 - 1
src/bin/zonemgr/zonemgr.py.in

@@ -42,7 +42,7 @@ from isc.log_messages.zonemgr_messages import *
 from isc.notify import notify_out
 
 # Initialize logging for called modules.
-isc.log.init("b10-zonemgr")
+isc.log.init("b10-zonemgr", buffer=True)
 logger = isc.log.Logger("zonemgr")
 
 # Pending system-wide debug level definitions, the ones we

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

@@ -31,6 +31,7 @@ libb10_log_la_SOURCES += message_initializer.cc message_initializer.h
 libb10_log_la_SOURCES += message_reader.cc message_reader.h
 libb10_log_la_SOURCES += message_types.h
 libb10_log_la_SOURCES += output_option.cc output_option.h
+libb10_log_la_SOURCES += buffer_appender_impl.cc buffer_appender_impl.h
 
 EXTRA_DIST  = README
 EXTRA_DIST += logimpl_messages.mes

+ 13 - 1
src/lib/log/README

@@ -338,7 +338,8 @@ Variant #1, Used by Production Programs
 ---------------------------------------
 void isc::log::initLogger(const std::string& root,
                           isc::log::Severity severity = isc::log::INFO,
-                          int dbglevel = 0, const char* file = NULL);
+                          int dbglevel = 0, const char* file = NULL,
+                          bool buffer = false);
 
 This is the call that should be used by production programs:
 
@@ -359,6 +360,17 @@ file
 The name of a local message file.  This will be read and its definitions used
 to replace the compiled-in text of the messages.
 
+buffer
+If set to true, initial log messages will be internally buffered, until the
+first time a logger specification is processed. This way the program can
+use logging before even processing its logging configuration. As soon as any
+specification is processed (even an empty one), the buffered log messages will
+be flushed according to the specification. Note that if this option is used,
+the program SHOULD call one of the LoggerManager's process() calls (if you
+are using the built-in logging configuration handling in ModuleCCSession,
+this is automatically handled). If the program exits before this is done,
+all log messages are dumped in a raw format to stdout (so that no messages
+get lost).
 
 Variant #2, Used by Unit Tests
 ------------------------------

+ 96 - 0
src/lib/log/buffer_appender_impl.cc

@@ -0,0 +1,96 @@
+// Copyright (C) 2012  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 <log/buffer_appender_impl.h>
+
+#include <log4cplus/loglevel.h>
+#include <boost/scoped_ptr.hpp>
+#include <cstdio>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+BufferAppender::~BufferAppender() {
+    // If there is anything left in the buffer,
+    // it means no reconfig has been done, and
+    // we can assume the logging system was either
+    // never setup, or broke while doing so.
+    // So dump all that is left to stdout
+    try {
+        flushStdout();
+    } catch (...) {
+        // Ok if we can't even seem to dump to stdout, never mind.
+    }
+}
+
+void
+BufferAppender::flushStdout() {
+    // This does not show a bit of information normal log messages
+    // do, so perhaps we should try and setup a new logger here
+    // However, as this is called from a destructor, it may not
+    // be a good idea; as we can't reliably know whether in what
+    // state the logger instance is now (or what the specific logger's
+    // settings were).
+    LogEventList::const_iterator it;
+    for (it = stored_.begin(); it != stored_.end(); ++it) {
+        const std::string level(it->first);
+        LogEventPtr event(it->second);
+        std::printf("%s [%s]: %s\n", level.c_str(),
+                    event->getLoggerName().c_str(),
+                    event->getMessage().c_str());
+    }
+    stored_.clear();
+}
+
+void
+BufferAppender::flush() {
+    LogEventList stored_copy;
+    stored_.swap(stored_copy);
+
+    LogEventList::const_iterator it;
+    for (it = stored_copy.begin(); it != stored_copy.end(); ++it) {
+        LogEventPtr event(it->second);
+        log4cplus::Logger logger =
+            log4cplus::Logger::getInstance(event->getLoggerName());
+
+        logger.log(event->getLogLevel(), event->getMessage());
+    }
+    flushed_ = true;
+}
+
+size_t
+BufferAppender::getBufferSize() const {
+    return (stored_.size());
+}
+
+void
+BufferAppender::append(const log4cplus::spi::InternalLoggingEvent& event) {
+    if (flushed_) {
+        isc_throw(LogBufferAddAfterFlush,
+                  "Internal log buffer has been flushed already");
+    }
+    // get a clone, and put the pointer in a shared_ptr in the list
+    std::auto_ptr<log4cplus::spi::InternalLoggingEvent> event_aptr =
+        event.clone();
+    // Also store the string representation of the log level, to be
+    // used in flushStdout if necessary
+    stored_.push_back(LevelAndEvent(
+                log4cplus::LogLevelManager().toString(event.getLogLevel()),
+                LogEventPtr(event_aptr.release())));
+}
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc

+ 118 - 0
src/lib/log/buffer_appender_impl.h

@@ -0,0 +1,118 @@
+// Copyright (C) 2012  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.
+
+#ifndef LOG_BUFFER_H
+#define LOG_BUFFER_H
+
+#include <exceptions/exceptions.h>
+
+#include <log4cplus/logger.h>
+#include <log4cplus/spi/loggingevent.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+/// \brief Buffer add after flush
+///
+/// This exception is thrown if the log buffer's add() method
+/// is called after the log buffer has been flushed; the buffer
+/// is only supposed to be used once (until the first time a
+/// logger specification is processed)
+class LogBufferAddAfterFlush : public isc::Exception {
+public:
+    LogBufferAddAfterFlush(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
+};
+
+/// Convenience typedef for a pointer to a log event
+typedef boost::shared_ptr<log4cplus::spi::InternalLoggingEvent> LogEventPtr;
+
+/// Convenience typedef for a pair string/logeventptr, the string representing
+/// the logger level, as returned by LogLevelManager::toString() at the
+/// time of initial logging
+typedef std::pair<std::string, LogEventPtr> LevelAndEvent;
+
+/// Convenience typedef for a vector of LevelAndEvent instances
+typedef std::vector<LevelAndEvent> LogEventList;
+
+/// \brief Buffering Logger Appender
+///
+/// This class can be set as an Appender for log4cplus loggers,
+/// and is used to store logging events; it simply keeps any
+/// event that is passed to \c append(), and will replay them to the
+/// logger that they were originally created for when \c flush() is
+/// called.
+///
+/// The idea is that initially, a program may want to do some logging,
+/// but does not know where to yet (for instance because it has yet to
+/// read and parse its configuration). Any log messages before this time
+/// would normally go to some default (say, stdout), and be lost in the
+/// real logging destination. By buffering them (and flushing them once
+/// the logger has been configured), these log messages are kept in a
+/// consistent place, and are not lost.
+///
+/// Given this goal, this class has an extra check; it will raise
+/// an exception if \c append() is called after flush().
+///
+/// If the BufferAppender instance is destroyed before being flushed,
+/// it will dump any event it has left to stdout.
+class BufferAppender : public log4cplus::Appender {
+public:
+    /// \brief Constructor
+    ///
+    /// Constructs a BufferAppender that buffers log evens
+    BufferAppender() : flushed_(false) {}
+
+    /// \brief Destructor
+    ///
+    /// Any remaining events are flushed to stdout (there should
+    /// only be any events remaining if flush() was never called)
+    virtual ~BufferAppender();
+
+    /// \brief Close the appender
+    ///
+    /// This class has no specialized handling for this method
+    virtual void close() {}
+
+    /// \brief Flush the internal buffer
+    ///
+    /// Events that have been stored (after calls to \c append()
+    /// are replayed to the logger. Should only be called after
+    /// new appenders have been set to the logger.
+    void flush();
+
+    /// \brief Returns the number of stored logging events
+    ///
+    /// Mainly useful for testing
+    size_t getBufferSize() const;
+
+protected:
+    virtual void append(const log4cplus::spi::InternalLoggingEvent& event);
+private:
+    /// \brief Helper for the destructor, flush events to stdout
+    void flushStdout();
+
+    LogEventList stored_;
+    bool flushed_;
+};
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
+
+#endif // LOG_BUFFER_H
+

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

@@ -94,7 +94,7 @@ LoggerManager::processEnd() {
 
 void
 LoggerManager::init(const std::string& root, isc::log::Severity severity,
-                    int dbglevel, const char* file)
+                    int dbglevel, const char* file, bool buffer)
 {
     // Load in the messages declared in the program and registered by
     // statically-declared MessageInitializer objects.
@@ -114,7 +114,9 @@ LoggerManager::init(const std::string& root, isc::log::Severity severity,
 
     // Initialize the implementation logging.  After this point, some basic
     // logging has been set up and messages can be logged.
-    LoggerManagerImpl::init(severity, dbglevel);
+    // However, they will not appear until a logging specification has been
+    // processed (or the program exits), see TODO
+    LoggerManagerImpl::init(severity, dbglevel, buffer);
     setLoggingInitialized();
 
     // Check if there were any duplicate message IDs in the default dictionary

+ 22 - 2
src/lib/log/logger_manager.h

@@ -76,6 +76,21 @@ public:
         processEnd();
     }
 
+    /// \brief Process 'empty' specification
+    ///
+    /// This will disable any existing output options, and set
+    /// the logging to go to the built-in default (stdout).
+    /// If the logger has been initialized with buffering enabled,
+    /// all log messages up to now shall be printed to stdout.
+    ///
+    /// This is mainly useful in scenarios where buffering is needed,
+    /// but it turns out there are no logging specifications to
+    /// handle.
+    void process() {
+        processInit();
+        processEnd();
+    }
+
     /// \brief Run-Time Initialization
     ///
     /// Performs run-time initialization of the logger system, in particular
@@ -91,13 +106,18 @@ public:
     /// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
     /// \param file Name of the local message file.  This must be NULL if there
     ///        is no local message file.
+    /// \param buffer If true, all log messages will be buffered until one of
+    ///        the \c process() methods is called. If false, initial logging
+    ///        shall go to the default output (i.e. stdout)
     static void init(const std::string& root,
                     isc::log::Severity severity = isc::log::INFO,
-                    int dbglevel = 0, const char* file = NULL);
+                    int dbglevel = 0, const char* file = NULL,
+                    bool buffer = false);
 
     /// \brief Reset logging
     ///
-    /// Resets logging to whatever was set in the call to init().
+    /// Resets logging to whatever was set in the call to init(), expect for
+    /// the buffer option.
     static void reset();
 
     /// \brief Read local message file

+ 61 - 19
src/lib/log/logger_manager_impl.cc

@@ -23,12 +23,14 @@
 #include <log4cplus/syslogappender.h>
 
 #include <log/logger.h>
+#include <log/logger_support.h>
 #include <log/logger_level_impl.h>
 #include <log/logger_manager.h>
 #include <log/logger_manager_impl.h>
 #include <log/log_messages.h>
 #include <log/logger_name.h>
 #include <log/logger_specification.h>
+#include <log/buffer_appender_impl.h>
 
 using namespace std;
 
@@ -40,19 +42,24 @@ namespace log {
 // 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.)
-
 void
 LoggerManagerImpl::processInit() {
+    storeBufferAppenders();
+
     log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
     initRootLogger();
 }
 
+// Flush the BufferAppenders at the end of processing a new specification
+void
+LoggerManagerImpl::processEnd() {
+    flushBufferAppenders();
+}
+
 // Process logging specification.  Set up the common states then dispatch to
 // add output specifications.
-
 void
 LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
-
     log4cplus::Logger logger = log4cplus::Logger::getInstance(
                                    expandLoggerName(spec.getName()));
 
@@ -65,8 +72,7 @@ LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
 
     // Output options given?
     if (spec.optionCount() > 0) {
-
-        // Yes, so replace all appenders for this logger.
+        // Replace all appenders for this logger.
         logger.removeAllAppenders();
 
         // Now process output specifications.
@@ -134,7 +140,17 @@ LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
     logger.addAppender(fileapp);
 }
 
-// Syslog appender. 
+void
+LoggerManagerImpl::createBufferAppender(log4cplus::Logger& logger) {
+    log4cplus::SharedAppenderPtr bufferapp(new internal::BufferAppender());
+    bufferapp->setName("buffer");
+    logger.addAppender(bufferapp);
+    // Since we do not know at what level the loggers will end up
+    // running, set it to the highest for now
+    logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+}
+
+// Syslog appender.
 void
 LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
                                          const OutputOption& opt)
@@ -147,10 +163,10 @@ LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
 
 
 // One-time initialization of the log4cplus system
-
 void
-LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel,
+                        bool buffer)
+{
     // Set up basic configurator.  This attaches a ConsoleAppender to the
     // root logger with suitable output.  This is used until we we have
     // actually read the logging configuration, in which case the output
@@ -161,22 +177,22 @@ LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
     // Add the additional debug levels
     LoggerLevelImpl::init();
 
-    reset(severity, dbglevel);
+    initRootLogger(severity, dbglevel, buffer);
 }
 
 // Reset logging to default configuration.  This closes all appenders
 // and resets the root logger to output INFO messages to the console.
 // It is principally used in testing.
 void
-LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel)
+{
     // Initialize the root logger
     initRootLogger(severity, dbglevel);
 }
 
 // Initialize the root logger
 void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
-                                       int dbglevel)
+                                       int dbglevel, bool buffer)
 {
     log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
 
@@ -191,14 +207,14 @@ void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
     b10root.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
                                                     Level(severity, dbglevel)));
 
-    // Set the BIND 10 root to use a console logger.
-    OutputOption opt;
-    createConsoleAppender(b10root, opt);
+    if (buffer) {
+        createBufferAppender(b10root);
+    } else {
+        OutputOption opt;
+        createConsoleAppender(b10root, opt);
+    }
 }
 
-// Set the the "console" layout for the given appenders.  This layout includes
-// a date/time and the name of the logger.
-
 void LoggerManagerImpl::setConsoleAppenderLayout(
         log4cplus::SharedAppenderPtr& appender)
 {
@@ -225,5 +241,31 @@ void LoggerManagerImpl::setSyslogAppenderLayout(
     appender->setLayout(layout);
 }
 
+void LoggerManagerImpl::storeBufferAppenders() {
+    // Walk through all loggers, and find any buffer appenders there
+    log4cplus::LoggerList loggers = log4cplus::Logger::getCurrentLoggers();
+    log4cplus::LoggerList::iterator it;
+    for (it = loggers.begin(); it != loggers.end(); ++it) {
+        log4cplus::SharedAppenderPtr buffer_appender =
+            it->getAppender("buffer");
+        if (buffer_appender) {
+            buffer_appender_store_.push_back(buffer_appender);
+        }
+    }
+}
+
+void LoggerManagerImpl::flushBufferAppenders() {
+    std::vector<log4cplus::SharedAppenderPtr> copy;
+    buffer_appender_store_.swap(copy);
+
+    std::vector<log4cplus::SharedAppenderPtr>::iterator it;
+    for (it = copy.begin(); it != copy.end(); ++it) {
+        internal::BufferAppender* app =
+            dynamic_cast<internal::BufferAppender*>(it->get());
+        assert(app != NULL);
+        app->flush();
+    }
+}
+
 } // namespace log
 } // namespace isc

+ 41 - 9
src/lib/log/logger_manager_impl.h

@@ -51,15 +51,14 @@ class LoggerManagerImpl {
 public:
 
     /// \brief Constructor
-    LoggerManagerImpl()
-    {}
+    LoggerManagerImpl() {}
 
     /// \brief Initialize Processing
     ///
     /// This resets the hierachy of loggers back to their defaults.  This means
     /// that all non-root loggers (if they exist) are set to NOT_SET, and the
     /// root logger reset to logging informational messages.
-    static void processInit();
+    void processInit();
 
     /// \brief Process Specification
     ///
@@ -71,8 +70,7 @@ public:
     /// \brief End Processing
     ///
     /// Terminates the processing of the logging specifications.
-    static void processEnd()
-    {}
+    void processEnd();
 
     /// \brief Implementation-specific initialization
     ///
@@ -87,8 +85,11 @@ public:
     ///
     /// \param severity Severity to be associated with this logger
     /// \param dbglevel Debug level associated with the root logger
+    /// \param buffer If true, all log messages will be buffered until one of
+    ///        the \c process() methods is called. If false, initial logging
+    ///        shall go to the default output (i.e. stdout)
     static void init(isc::log::Severity severity = isc::log::INFO,
-                     int dbglevel = 0);
+                     int dbglevel = 0, bool buffer = false);
 
     /// \brief Reset logging
     ///
@@ -132,15 +133,27 @@ private:
     static void createSyslogAppender(log4cplus::Logger& logger,
                                      const OutputOption& opt);
 
+    /// \brief Create buffered appender
+    ///
+    /// Appends an object to the logger that will store the log events sent
+    /// to the logger. These log messages are replayed to the logger in
+    /// processEnd().
+    ///
+    /// \param logger Log4cplus logger to which the appender must be attached.
+    static void createBufferAppender(log4cplus::Logger& logger);
+
     /// \brief Set default layout and severity for root logger
     ///
-    /// Initializes the root logger to BIND 10 defaults - console output and
-    /// the passed severity/debug level.
+    /// Initializes the root logger to BIND 10 defaults - console or buffered
+    /// output and the passed severity/debug level.
     ///
     /// \param severity Severity of messages that the logger should output.
     /// \param dbglevel Debug level if severity = DEBUG
+    /// \param buffer If true, all log messages will be buffered until one of
+    ///        the \c process() methods is called. If false, initial logging
+    ///        shall go to the default output (i.e. stdout)
     static void initRootLogger(isc::log::Severity severity = isc::log::INFO,
-                               int dbglevel = 0);
+                               int dbglevel = 0, bool buffer = false);
 
     /// \brief Set layout for console appender
     ///
@@ -161,6 +174,25 @@ private:
     ///
     /// \param appender Appender for which this pattern is to be set.
     static void setSyslogAppenderLayout(log4cplus::SharedAppenderPtr& appender);
+
+    /// \brief Store all buffer appenders
+    ///
+    /// When processing a new specification, this method can be used
+    /// to keep a list of the buffer appenders; the caller can then
+    /// process the specification, and call \c flushBufferAppenders()
+    /// to flush and clear the list
+    void storeBufferAppenders();
+
+    /// \brief Flush the stored buffer appenders
+    ///
+    /// This flushes the list of buffer appenders stored in
+    /// \c storeBufferAppenders(), and clears it
+    void flushBufferAppenders();
+
+    /// Only used between processInit() and processEnd(), to temporarily
+    /// store the buffer appenders in order to flush them after
+    /// processSpecification() calls have been completed
+    std::vector<log4cplus::SharedAppenderPtr> buffer_appender_store_;
 };
 
 } // namespace log

+ 2 - 2
src/lib/log/logger_support.cc

@@ -46,8 +46,8 @@ setLoggingInitialized(bool state) {
 
 void
 initLogger(const string& root, isc::log::Severity severity, int dbglevel,
-    const char* file) {
-    LoggerManager::init(root, severity, dbglevel, file);
+           const char* file, bool buffer) {
+    LoggerManager::init(root, severity, dbglevel, file, buffer);
 }
 
 } // namespace log

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

@@ -61,9 +61,13 @@ void setLoggingInitialized(bool state = true);
 /// \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.
+/// \param buffer If true, all log messages will be buffered until one of
+///        the \c process() methods is called. If false, initial logging
+///        shall go to the default output (i.e. stdout)
 void initLogger(const std::string& root,
                 isc::log::Severity severity = isc::log::INFO,
-                int dbglevel = 0, const char* file = NULL);
+                int dbglevel = 0, const char* file = NULL,
+                bool buffer = false);
 
 } // namespace log
 } // namespace isc

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

@@ -37,6 +37,15 @@ init_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
 
+noinst_PROGRAMS += buffer_logger_test
+buffer_logger_test_SOURCES = buffer_logger_test.cc
+buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
+buffer_logger_test_LDADD  = $(top_builddir)/src/lib/log/libb10-log.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+buffer_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+
 noinst_PROGRAMS += logger_lock_test
 logger_lock_test_SOURCES = logger_lock_test.cc
 nodist_logger_lock_test_SOURCES = log_test_messages.cc log_test_messages.h
@@ -82,11 +91,13 @@ run_unittests_SOURCES += logger_specification_unittest.cc
 run_unittests_SOURCES += message_dictionary_unittest.cc
 run_unittests_SOURCES += message_reader_unittest.cc
 run_unittests_SOURCES += output_option_unittest.cc
+run_unittests_SOURCES += buffer_appender_unittest.cc
 nodist_run_unittests_SOURCES = log_test_messages.cc log_test_messages.h
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
 run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 run_unittests_LDADD    = $(AM_LDADD)
+run_unittests_LDADD    +=  $(LOG4CPLUS_LIBS)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)
 
 # logging initialization tests.  These are put in separate programs to
@@ -124,6 +135,7 @@ check-local:
 	$(SHELL) $(abs_builddir)/console_test.sh
 	$(SHELL) $(abs_builddir)/destination_test.sh
 	$(SHELL) $(abs_builddir)/init_logger_test.sh
+	$(SHELL) $(abs_builddir)/buffer_logger_test.sh
 	$(SHELL) $(abs_builddir)/local_file_test.sh
 	$(SHELL) $(abs_builddir)/logger_lock_test.sh
 	$(SHELL) $(abs_builddir)/severity_test.sh
@@ -131,6 +143,7 @@ check-local:
 noinst_SCRIPTS  = console_test.sh
 noinst_SCRIPTS += destination_test.sh
 noinst_SCRIPTS += init_logger_test.sh
+noinst_SCRIPTS += buffer_logger_test.sh
 noinst_SCRIPTS += local_file_test.sh
 noinst_SCRIPTS += logger_lock_test.sh
 noinst_SCRIPTS += severity_test.sh

+ 146 - 0
src/lib/log/tests/buffer_appender_unittest.cc

@@ -0,0 +1,146 @@
+// Copyright (C) 2012  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 "config.h"
+#include <gtest/gtest.h>
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include <log/buffer_appender_impl.h>
+
+#include <log4cplus/loggingmacros.h>
+#include <log4cplus/logger.h>
+#include <log4cplus/nullappender.h>
+#include <log4cplus/spi/loggingevent.h>
+
+using namespace isc::log;
+using namespace isc::log::internal;
+
+namespace isc {
+namespace log {
+
+/// \brief Specialized test class
+///
+/// In order to test append() somewhat directly, this
+/// class implements one more method (addEvent)
+class TestBufferAppender : public BufferAppender {
+public:
+    void addEvent(const log4cplus::spi::InternalLoggingEvent& event) {
+        append(event);
+    }
+};
+
+class BufferAppenderTest : public ::testing::Test {
+protected:
+    BufferAppenderTest() : buffer_appender1(new TestBufferAppender()),
+                      appender1(buffer_appender1),
+                      buffer_appender2(new TestBufferAppender()),
+                      appender2(buffer_appender2),
+                      logger(log4cplus::Logger::getInstance("buffer"))
+    {
+        logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+    }
+
+    ~BufferAppenderTest() {
+        // If any log messages are left, we don't care, get rid of them,
+        // by flushing them to a null appender
+        // Given the 'messages should not get lost' approach of the logging
+        // system, not flushing them to a null appender would cause them
+        // to be dumped to stdout as the test is destroyed, making
+        // unnecessarily messy test output.
+        log4cplus::SharedAppenderPtr null_appender(
+            new log4cplus::NullAppender());
+        logger.removeAllAppenders();
+        logger.addAppender(null_appender);
+        buffer_appender1->flush();
+        buffer_appender2->flush();
+    }
+
+    TestBufferAppender* buffer_appender1;
+    log4cplus::SharedAppenderPtr appender1;
+    TestBufferAppender* buffer_appender2;
+    log4cplus::SharedAppenderPtr appender2;
+    log4cplus::Logger logger;
+};
+
+// Test that log events are indeed stored, and that they are
+// flushed to the new appenders of their logger
+TEST_F(BufferAppenderTest, flush) {
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+    ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+    // Create a Logger, log a few messages with the first appender
+    logger.addAppender(appender1);
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(1, buffer_appender1->getBufferSize());
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(2, buffer_appender1->getBufferSize());
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(3, buffer_appender1->getBufferSize());
+
+    // Second buffer should still be empty
+    ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+    // Replace the appender by the second one, and call flush;
+    // this should cause all events to be moved to the second buffer
+    logger.removeAllAppenders();
+    logger.addAppender(appender2);
+    buffer_appender1->flush();
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+    ASSERT_EQ(3, buffer_appender2->getBufferSize());
+}
+
+// Once flushed, logging new messages with the same buffer should fail
+TEST_F(BufferAppenderTest, addAfterFlush) {
+    logger.addAppender(appender1);
+    buffer_appender1->flush();
+    EXPECT_THROW(LOG4CPLUS_INFO(logger, "Foo"), LogBufferAddAfterFlush);
+    // It should not have been added
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+
+    // But logging should work again as long as a different buffer is used
+    logger.removeAllAppenders();
+    logger.addAppender(appender2);
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(1, buffer_appender2->getBufferSize());
+}
+
+TEST_F(BufferAppenderTest, addDirectly) {
+    // A few direct calls
+    log4cplus::spi::InternalLoggingEvent event("buffer",
+                                               log4cplus::INFO_LOG_LEVEL,
+                                               "Bar", "file", 123);
+    buffer_appender1->addEvent(event);
+    ASSERT_EQ(1, buffer_appender1->getBufferSize());
+
+    // Do one from a smaller scope to make sure destruction doesn't harm
+    {
+        log4cplus::spi::InternalLoggingEvent event2("buffer",
+                                                    log4cplus::INFO_LOG_LEVEL,
+                                                    "Bar", "file", 123);
+        buffer_appender1->addEvent(event2);
+    }
+    ASSERT_EQ(2, buffer_appender1->getBufferSize());
+
+    // And flush them to the next
+    logger.removeAllAppenders();
+    logger.addAppender(appender2);
+    buffer_appender1->flush();
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+    ASSERT_EQ(2, buffer_appender2->getBufferSize());
+}
+
+}
+}

+ 71 - 0
src/lib/log/tests/buffer_logger_test.cc

@@ -0,0 +1,71 @@
+// Copyright (C) 2012  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 <log/macros.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <log/log_messages.h>
+#include <util/interprocess_sync_null.h>
+
+using namespace isc::log;
+
+namespace {
+void usage() {
+    std::cout << "Usage: buffer_logger_test [-n]" << std::endl;
+}
+} // end unnamed namespace
+
+/// \brief Test InitLogger
+///
+/// A program used in testing the logger that initializes logging with
+/// buffering enabled, so that initial log messages are not immediately
+/// logged, but are not lost (they should be logged the moment process()
+/// is called.
+///
+/// If -n is given as an argument, process() is never called. In this
+/// case, upon exit, all leftover log messages should be printed to
+/// stdout, but without normal logging additions (such as time and
+/// logger name)
+int
+main(int argc, char** argv) {
+    bool do_process = true;
+    int opt;
+    while ((opt = getopt(argc, argv, "n")) != -1) {
+        switch (opt) {
+        case 'n':
+            do_process = false;
+            break;
+        default:
+            usage();
+            return (1);
+        }
+    }
+
+    // Note, level is INFO, so DEBUG should normally not show
+    // up. Unless process is never called (at which point it
+    // will end up in the dump at the end).
+    initLogger("buffertest", isc::log::INFO, 0, NULL, true);
+    Logger logger("log");
+    // No need for file interprocess locking in this test
+    logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+    LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+    LOG_DEBUG(logger, 50, LOG_BAD_DESTINATION).arg("debug-50");
+    LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+    // process should cause them to be logged
+    if (do_process) {
+        LoggerManager logger_manager;
+        logger_manager.process();
+    }
+    return (0);
+}

+ 61 - 0
src/lib/log/tests/buffer_logger_test.sh.in

@@ -0,0 +1,61 @@
+#!/bin/sh
+# Copyright (C) 2012  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.
+
+# Checks that the initLogger() call uses for unit tests respects the setting of
+# the buffer value
+#
+
+testname="bufferLogger test"
+echo $testname
+
+failcount=0
+tempfile=@abs_builddir@/buffer_logger_test_tempfile_$$
+
+passfail() {
+    if [ $1 -eq 0 ]; then
+        echo " pass"
+    else
+        echo " FAIL"
+        failcount=`expr $failcount + $1`
+    fi
+}
+
+echo "1. Checking that buffer initialization works"
+
+echo -n  "   - Buffer including process() call: "
+cat > $tempfile << .
+INFO  [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+INFO  [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test 2>&1 | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+    cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n  "   - Buffer excluding process() call: "
+cat > $tempfile << .
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+DEBUG [buffertest.log]: LOG_BAD_DESTINATION unrecognized log destination: debug-50
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test -n 2>&1 | diff $tempfile -
+passfail $?
+
+
+
+# Tidy up.
+rm -f $tempfile
+
+exit $failcount

+ 7 - 2
src/lib/python/isc/config/cfgmgr.py

@@ -34,7 +34,7 @@ import bind10_config
 import isc.log
 from isc.log_messages.cfgmgr_messages import *
 
-logger = isc.log.Logger("cfgmgr")
+logger = isc.log.Logger("cfgmgr", buffer=True)
 
 class ConfigManagerDataReadError(Exception):
     """This exception is thrown when there is an error while reading
@@ -224,8 +224,13 @@ class ConfigManager:
 
     def check_logging_config(self, config):
         if self.log_module_name in config:
+            # If there is logging config, apply it.
             ccsession.default_logconfig_handler(config[self.log_module_name],
                                                 self.log_config_data)
+        else:
+            # If there is no logging config, we still need to trigger the
+            # handler, so make it use defaults (and flush any buffered logs)
+            ccsession.default_logconfig_handler({}, self.log_config_data)
 
     def notify_boss(self):
         """Notifies the Boss module that the Config Manager is running"""
@@ -313,11 +318,11 @@ class ConfigManager:
             self.config = ConfigManagerData.read_from_file(self.data_path,
                                                            self.\
                                                            database_filename)
-            self.check_logging_config(self.config.data);
         except ConfigManagerDataEmpty:
             # ok, just start with an empty config
             self.config = ConfigManagerData(self.data_path,
                                             self.database_filename)
+        self.check_logging_config(self.config.data);
 
     def write_config(self):
         """Write the current configuration to the file specificied at init()"""

+ 21 - 8
src/lib/python/isc/log/log.cc

@@ -166,17 +166,23 @@ reset(PyObject*, PyObject*) {
 }
 
 PyObject*
-init(PyObject*, PyObject* args) {
+init(PyObject*, PyObject* args, PyObject* arg_keywords) {
     const char* root;
     const char* file(NULL);
     const char* severity("INFO");
+    bool buffer = false;
     int dbglevel(0);
-    if (!PyArg_ParseTuple(args, "s|siz", &root, &severity, &dbglevel, &file)) {
+    const char* const keywords[] = { "name", "severity", "debuglevel", "file",
+                                     "buffer", NULL };
+    if (!PyArg_ParseTupleAndKeywords(args, arg_keywords, "s|sizb",
+                                     const_cast<char**>(keywords), &root,
+                                     &severity, &dbglevel, &file, &buffer)) {
         return (NULL);
     }
 
     try {
-        LoggerManager::init(root, getSeverity(severity), dbglevel, file);
+        LoggerManager::init(root, getSeverity(severity), dbglevel, file,
+                            buffer);
     }
     catch (const std::exception& e) {
         PyErr_SetString(PyExc_RuntimeError, e.what());
@@ -266,12 +272,19 @@ PyMethodDef methods[] = {
         "need to call it. It returns None if the message does not exist."},
     {"reset", reset, METH_NOARGS,
         "Reset all logging. For testing purposes only, do not use."},
-    {"init", init, METH_VARARGS,
+    {"init", reinterpret_cast<PyCFunction>(init), METH_VARARGS | METH_KEYWORDS,
         "Run-time initialization. You need to call this before you do any "
         "logging, to configure the root logger name. You may also provide "
-        "logging severity (one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
-        "'FATAL'), a debug level (integer in the range 0-99) and a file name "
-        "of a dictionary with message text translations."},
+        "Arguments:\n"
+        "name: root logger name\n"
+        "severity (optional): one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
+        "'FATAL'\n"
+        "debuglevel (optional): a debug level (integer in the range 0-99) "
+        "file (optional): a file name of a dictionary with message text "
+        "translations\n"
+        "buffer (optional), boolean, when True, causes all log messages "
+        "to be stored internally until log_config_update is called, at "
+        "which point they shall be logged."},
     {"resetUnitTestRootLogger", resetUnitTestRootLogger, METH_VARARGS,
         "Resets the configuration of the root logger to that set by the "
         "B10_XXX environment variables.  It is aimed at unit tests, where "
@@ -655,7 +668,7 @@ PyTypeObject logger_type = {
     NULL,                               // tp_as_number
     NULL,                               // tp_as_sequence
     NULL,                               // tp_as_mapping
-    NULL,                               // tp_hash 
+    NULL,                               // tp_hash
     NULL,                               // tp_call
     NULL,                               // tp_str
     NULL,                               // tp_getattro

+ 22 - 0
src/lib/python/isc/log/tests/log_test.py

@@ -56,6 +56,28 @@ class Manager(unittest.TestCase):
         # ignore errors like missing file?
         isc.log.init("root", "INFO", 0, "/no/such/file");
 
+    def test_init_keywords(self):
+        isc.log.init(name="root", severity="DEBUG", debuglevel=50, file=None,
+                     buffer=True)
+        # unknown keyword
+        self.assertRaises(TypeError, isc.log.init, name="root", foo="bar")
+        # Replace the values for each keyword by a wrong type, one by one
+        self.assertRaises(TypeError, isc.log.init, name=1,
+                          severity="DEBUG", debuglevel=50, file=None,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity=2, debuglevel=50, file=None,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity="DEBUG", debuglevel="50", file=None,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity="DEBUG", debuglevel=50, file=1,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity="DEBUG", debuglevel=50, file=None,
+                          buffer=None)
+
     def test_log_config_update(self):
         log_spec = json.dumps(isc.config.module_spec_from_file(path_search('logging.spec', bind10_config.PLUGIN_PATHS)).get_full_spec())