Parcourir la source

Check-in of some code prior to conversion of repository to git; the code is not finished!

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac438@4169 e5f2f494-b856-4b98-b285-d166d9295462
Stephen Morris il y a 14 ans
Parent
commit
ebb344c1e2

+ 35 - 0
configure.ac

@@ -327,6 +327,39 @@ if test "$lcov" != "no"; then
 fi
 fi
 AC_SUBST(USE_LCOV)
 AC_SUBST(USE_LCOV)
 
 
+# Configure log4cxx header and library path
+#
+# If explicitly specified, use it.
+
+AC_ARG_WITH([log4cxx],
+  AC_HELP_STRING([--with-log4cxx=PATH],
+    [specify directory where log4cxx is installed]),
+  [log4cxx_include_path="${withval}/include"])
+
+# If not specified, try some common paths.  These default to
+# /usr/include if not found
+
+if test -z "$with_log4cxx"; then
+	log4cxxdirs="/usr/local /usr/pkg /opt /opt/local"
+	for d in $log4cxxdirs
+	do
+		if test -d $d/include/log4cxx; then
+			log4cxx_include_path=$d/include
+			break
+		fi
+	done
+fi
+
+CPPFLAGS_SAVES="$CPPFLAGS"
+if test "${log4cxx_include_path}" ; then
+	LOG4CXX_INCLUDES="-I${log4cxx_include_path}"
+	CPPFLAGS="$CPPFLAGS $LOG4CXX_INCLUDES"
+fi
+AC_CHECK_HEADER([log4cxx/logger.h],, AC_MSG_ERROR([Missing log4cxx header files.]))
+CPPFLAGS="$CPPFLAGS_SAVES"
+AC_SUBST(LOG4CXX_INCLUDES)
+
+
 #
 #
 # Configure Boost header path
 # Configure Boost header path
 #
 #
@@ -612,6 +645,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/xfr/Makefile
                  src/lib/xfr/Makefile
                  src/lib/log/Makefile
                  src/lib/log/Makefile
+                 src/lib/log/tests/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/testdata/Makefile
                  src/lib/testutils/testdata/Makefile
                  src/lib/nsas/Makefile
                  src/lib/nsas/Makefile
@@ -721,6 +755,7 @@ dnl includes too
                  ${PYTHON_LDFLAGS}
                  ${PYTHON_LDFLAGS}
                  ${PYTHON_LIB}
                  ${PYTHON_LIB}
   Boost:         ${BOOST_INCLUDES}
   Boost:         ${BOOST_INCLUDES}
+  log4cxx:       ${LOG4CXX_INCLUDES}
   SQLite:        $SQLITE_CFLAGS
   SQLite:        $SQLITE_CFLAGS
                  $SQLITE_LIBS
                  $SQLITE_LIBS
 
 

+ 25 - 2
src/lib/log/Makefile.am

@@ -1,4 +1,27 @@
-AM_CXXFLAGS = $(B10_CXXFLAGS)
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+
+CLEANFILES = *.gcno *.gcda
 
 
 lib_LTLIBRARIES = liblog.la
 lib_LTLIBRARIES = liblog.la
-liblog_la_SOURCES = dummylog.cc dummylog.h
+liblog_la_SOURCES  = root_logger_name.cc root_logger_name.h
+liblog_la_SOURCES += xdebuglevel.cc xdebuglevel.h
+liblog_la_SOURCES += logger.cc logger.h
+liblog_la_SOURCES += message_reader.cc message_reader.h
+liblog_la_SOURCES += filename.h filename.cc
+liblog_la_SOURCES += stringutil.h stringutil.cc
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+liblog_la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_GXX
+liblog_la_CXXFLAGS += -Wno-unused-parameter
+endif
+if USE_CLANGPP
+# Same for clang++, but we need to turn off -Werror completely.
+liblog_la_CXXFLAGS += -Wno-error
+endif
+liblog_la_CPPFLAGS = $(AM_CPPFLAGS)

+ 72 - 0
src/lib/log/Message.h

@@ -0,0 +1,72 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __MESSAGE_H
+#define __MESSAGE_H
+
+/// \brief Log Message
+///
+/// This class represents a message in which information about a logging event
+/// is encoded.  This is invisible to the author of code doing logging unless
+/// they are planning on writing an Appender.
+
+class Message {
+public:
+
+    // The following is a placeholder.  It will be replaced with a finer-
+    // grained time definition during implementation.
+    typedef time_t  Time;   ///< To be replaced with a finer-grained time later
+
+    // Constructor/destructor stll to be determined
+
+    /// \brief Return Timestamp
+    ///
+    /// \return Timestamp associated with the message.
+    Time getTimestamp() const;
+
+    /// \brief Return Source
+    ///
+    /// \return Returns the source of the message.  This is a "."-separated
+    /// string containing the hierarchy of the logger than generated this
+    /// message.
+    std::string getSource() const;
+
+    /// \brief Return ID
+    ///
+    /// \return Returns the ID of the message, a 32-bit integer.
+    uint32_t getId() const;
+
+    /// \brief Return Parameters
+    ///
+    /// \return The parameters of the message in the form of a vector of
+    /// strings.  Numeric parameters have been converted to strings and
+    /// included in the message.
+    std::vector<std::string> getParameters() const;
+
+    /// \brief Return Encoded Message
+    ///
+    /// The contents of the message are encoded as a string in the form
+    ///
+    /// <message ID><'\0'><param 1><'\0'>...
+    ///
+    /// Some Appenders may find this format useful, so the ability to retrieve
+    /// it directly is provided.
+    std::string getRawMessage() const;
+};
+
+
+
+#endif // __MESSAGE_H

+ 119 - 0
src/lib/log/README.txt

@@ -0,0 +1,119 @@
+Logging Messages
+
+Message Storage
+===============
+Each message is identified by a string identifier, e.g. "INVFILNAM", which is
+associated with some text (e.g. "%s is an invalid file name").  These are stored
+in a single std::map, in a class called the Dictionary.
+
+The message identifier (along with parameters) is passed through the logging
+system to an appender, which uses the identifier to look up the message in
+the dictionary.  The message is then formatted and written out.
+
+Message File
+============
+A message file is a file containing message definitions.  Typically there will
+be one message file for each component that declares message symbols.
+
+
+A example file could
+be:
+
+# Example message file
+# $ID:$
+
+$PREFIX TEST_
+TEST1       message %s is much too large
++ This message is a test for the general message code
+
+UNKNOWN     unknown message
++ Issued when the message is unknown.
+
+Point to note:
+* Leading and trailing space are trimmed from the line.
+* Blank lines are ignored
+* Lines starting with "#" are comments are are ignored.
+* Lines starting $ are directives.  At present, the only directive recognised
+  is $PREFIX, which has one argument: the string used to prefix symbols.  If
+  there is no facility directive, there is no prefix to the symbols.
+* Lines starting + indicate an explanation for the preceding message.  These
+  are processed by a separate program and used to generate an error messages
+  manual.  However they are treated like comments here.
+* Message lines.  These comprise a symbol name and a message (which includes
+  C-style substitution strings).
+
+Message Compiler
+================
+The message compiler produces two files:
+
+1) A C++ header file (called <message-file-name>.h) that holds lines of the
+form:
+
+namespace {
+const char* PREFIX_IDENTIFIER = "identifier";
+   :
+
+}
+
+These are just convenience symbols to avoid the need to type in the string in
+quotes.  PREFIX_ is the string in the $PREFIX directive and is used to avoid
+potential clashes with system-defined symbols.
+
+2) A C++ source file (called <message-file-name>.cpp) that holds the code
+to insert the symbols and messages into the map.
+
+This file declares an array of identifiers/messages in the form:
+
+namespace {
+const char* messages = {
+    identifier1, text1,
+    identifier2, text2,
+    :
+    NULL
+};
+}
+
+(A more complex structure to group identifiers and their messages could be
+imposed, but as the array is generated by code and will be read by code,
+it is not needed.)
+
+It then declares an object that will add information to the global dictionary:
+
+DictionaryAppender <message-file-name>_<prefix>_<time>(messages);
+
+(Declaring the object as "static" or in the anonymous namespace runs the risk
+of it being optimised away when the module is compiled with optimisation.  But
+giving it a standard name would cause a clash when multiple files are used,
+hence an attempt at disambiguation.)
+
+The constructor of the DictionaryAppender object retrieves the singleton
+global Dictionary object (created using standard methods to avoid the "static
+initialization fiasco") and adds each identifier and text to the member
+std::map.  A check is made as each is added; if the identifier already exists,
+it is added to "overflow" vector; the vector is printed to the main logging
+output when logging is finally enabled (to indicate a programming error).
+
+User Message Files
+==================
+During logging initialization, a search is made for a user message file in a
+specific location.  (The specification of the location has yet to be decided -
+it will probably be a command-line option.)  These messages are read into a
+local Dictionary object (which performs the same checks as the global
+Dictionary for duplicate messages).
+
+The local Dictionary is then merged with the global Dictionary.  In this case
+though, warnings are issued where a message does not replace one in the global
+Dictionary.
+
+An additional check that could be done is to compare the user message string
+with the main message string and to check that they have the same number of
+"%s" components.  This will avoid potential problems in message formatting. (As
+noted in another design document, the intention within logging is to convert
+all parameters to strings at the point at which the logging call is made.)
+
+Message Compiler Implementation
+===============================
+The fact that user files are read in at run-time implies that the code that
+reads the files should be C++.  An implication of this is that the message
+compiler should be written in C++ (instead of Python, which is probably
+better for the task) to avoid two sets of code where message files are parsed.

+ 41 - 0
src/lib/log/appender.h

@@ -0,0 +1,41 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __APPENDER_H
+#define __APPENDER_H
+
+#include <message.h>
+
+/// \brief Abstract Appender Class
+///
+/// This class is responsible for writing messages generated by loggers
+/// to destinations.  It is an abstract class; particular appenders will
+/// control different destinations.
+///
+/// Multiple appenders can be attached to a logger.  Output is routed to
+/// all appenders attached to a logger.  The logger hierachy is then walked
+/// and the message passed to the appenders in parent loggers.
+
+class AbstractAppender {
+public:
+    
+    /// \brief Write the Output
+    ///
+    /// Formats the message and writes it to the specified output.
+    void write(Message& message);
+};
+
+#endif // __APPENDER_H

+ 130 - 0
src/lib/log/compiler/message.cc

@@ -0,0 +1,130 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <iostream>
+#include <unistd.h>
+#include <getopt.h>
+
+using namespace std;
+
+static const char* VERSION = "1.0-0";
+
+/// \brief Message Compiler
+///
+/// \b Overview<BR>
+/// This is the program that takes as input a message file and produces:
+///
+/// \li A .h file containing message definition
+/// \li A .cc file containing code that adds the messages to the program's
+/// message disctionary at start-up time.
+///
+/// Alternatively, the program can produce a .py file that contains the
+/// message definitions.
+///
+
+/// \b Invocation<BR>
+/// The program is invoked with the command:
+///
+/// <tt>message [-p] \<message-file\></tt>
+///
+/// It reads the message file and writes out two files of the same name but with
+/// extensions of .h and .cc.
+///
+/// If \c -p is specified, the C++ files are not written; instead a Python file
+/// of the same name (but with the file extension .py) is written.
+
+
+/// \brief Print Version
+///
+/// Prints the program's version number.
+
+static void version() {
+    cout << VERSION << "\n";
+}
+
+/// \brief Print Usage
+///
+/// Prints program usage to stdout.
+
+static void usage() {
+    cout <<
+        "Usage: message [-h] [-p] [-v] <message-file>\n" <<
+        "\n" <<
+        "-h       Print this message and exit\n" <<
+        "-p       Output a Python module holding the message definitions.\n" <<
+        "         By default a C++ header file and implementation file are\n" <<
+        "         written.\n" <<
+        "-v       Print the program version and exit\n" <<
+        "\n" <<
+        "<message-file> is the name of the input message file.\n";
+}
+
+/// \brief Main Program
+///
+/// Parses the options then dispatches to the appropriate function.  See the
+/// main file header for the invocation.
+
+int main(int argc, char** argv) {
+    
+    const struct option loptions[] = {          // Long options
+        {"help",    no_argument, NULL, 'h'},
+        {"python",  no_argument, NULL, 'p'},
+        {"version", no_argument, NULL, 'v'},
+        {NULL,      0,           NULL, 0  }
+    };
+    const char* soptions = "hpv";               // Short options
+
+    optind = 1;             // Ensure we start a new scan
+    int  opt;               // Value of the option
+    bool python = false;    // Set true if the -p flag is detected
+
+    while ((opt = getopt_long(argc, argv, soptions, loptions, NULL)) != -1) {
+        switch (opt) {
+            case 'h':
+                usage();
+                return 0;
+
+            case 'v':
+                version();
+                return 0;
+
+            case 'p':
+                python = true;
+                break;
+
+            default:
+                // A message will have already been output about the error.
+                return 1;
+        }
+    }
+
+    // Do we have the message file?
+    if (optind < (argc - 1)) {
+        std::cout << "Error: excess arguments in command line\n";
+        usage();
+        return 1;
+    }
+    else if (optind >= argc) {
+        std::cout << "Error: missing message file\n";
+        usage();
+        return 1;
+    }
+
+    // Have identified the file, so process it.
+    MessageFileProcessor fileProcessor();
+    return fileProcessor.process(argv[argc - 1], python);
+
+}

+ 31 - 0
src/lib/log/dbglevels.h

@@ -0,0 +1,31 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __DBGLEVELS_H
+#define __DBGLEVELS_H
+
+/// \brief Defines Debug Levels
+///
+/// Defines the maximum and minimum debug levels and the number of levels.
+/// These are defined using #define as they are referenced in the construction
+/// of variables declared outside execution units.  (In this way we avoid the
+/// "static initialization fiasco" problem.
+
+#define MIN_DEBUG_LEVEL 0
+#define MAX_DEBUG_LEVEL 99
+#define NUM_DEBUG_LEVEL (MAX_DEBUG_LEVEL - MIN_DEBUG_LEVEL + 1)
+
+#endif // __DBGLEVELS_H

+ 132 - 0
src/lib/log/filename.cc

@@ -0,0 +1,132 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <iostream>
+#include <algorithm>
+#include <string>
+
+#include <ctype.h>
+
+#include <filename.h>
+
+using namespace std;
+
+
+namespace isc {
+namespace log {
+
+// Split string into components.  Any backslashes are assumed to have
+// been replaced by forward slashes.
+
+void Filename::split(const string& full_name, string& directory,
+    string& name, string& extension) const
+{
+    directory = name = extension = "";
+    bool dir_present = false;
+    if (! full_name.empty()) {
+
+        // Find the directory.
+        size_t last_slash = full_name.find_last_of('/');
+        if (last_slash != string::npos) {
+
+            // Found the last slash, so extract directory component and
+            // set where the scan for the last_dot should terminate.
+            directory = full_name.substr(0, last_slash + 1);
+            if (last_slash == full_name.size()) {
+
+                // The entire string was a directory, so exit not and don't
+                // do any more searching.
+                return;
+            }
+
+            // Found a directory so note the fact.
+            dir_present = true;
+        }
+
+        // Now search backwards for the last ".".
+        size_t last_dot = full_name.find_last_of('.');
+        if ((last_dot == string::npos) ||
+            (dir_present && (last_dot < last_slash))) {
+
+            // Last "." either not found or it occurs to the left of the last
+            // slash if a directory was present (so it is part of a directory
+            // name).  In this case, the remainder of the string after the slash
+            // is the name part.
+            name = full_name.substr(last_slash + 1);
+            return;
+        }
+
+        // Did find a valid dot, so it and everything to the right is the
+        // extension...
+        extension = full_name.substr(last_dot);
+
+        // ... and the name of the file is everything in between.
+        if ((last_dot - last_slash) > 1) {
+            name = full_name.substr(last_slash + 1, last_dot - last_slash - 1);
+        }
+    }
+
+}
+
+// Expand the stored filename with the default.
+
+string Filename::expandWithDefault(const string& defname) const {
+
+    string def_directory("");
+    string def_name("");
+    string def_extension("");
+
+    // Normalize the input string.
+    string copy_defname = StringUtil::trim(defname);
+    StringUtil::normalizeSlash(copy_defname);
+
+    // Split into the components
+    split(copy_defname, def_directory, def_name, def_extension);
+
+    // Now construct the result.
+    string retstring =
+        (directory_.empty() ? def_directory : directory_) +
+        (name_.empty() ? def_name : name_) +
+        (extension_.empty() ? def_extension : extension_);
+    return retstring;
+}
+
+// Use the stored name as default for a given name
+
+string Filename::useAsDefault(const string& name) const {
+
+    string name_directory("");
+    string name_name("");
+    string name_extension("");
+
+    // Normalize the input string.
+    string copy_name = StringUtil::trim(name);
+    StringUtil::normalizeSlash(copy_name);
+
+    // Split into the components
+    split(copy_name, name_directory, name_name, name_extension);
+
+    // Now construct the result.
+    string retstring =
+        (name_directory.empty() ? directory_ : name_directory) +
+        (name_name.empty() ? name_ : name_name) +
+        (name_extension.empty() ? extension_ : name_extension);
+    return retstring;
+}
+
+
+} // namespace log
+} // namespace isc

+ 163 - 0
src/lib/log/filename.h

@@ -0,0 +1,163 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __FILENAME_H
+#define __FILENAME_H
+
+#include <string>
+
+#include <stringutil.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Class to Manipulate Filenames
+///
+/// This is a utility class to manipulate filenames.  It repeats some of the
+/// features found in the Boost filename class, but is self-contained so avoids
+/// the need to link in the Boost library.
+///
+/// A Unix-style filename comprises three parts:
+///
+/// Directory - everything up to and including the last "/".  If there is no
+/// "/" in the string, there is no directory component.  Note that the
+/// requirement of a trailing slash eliminates the ambiguity of whether a
+/// component is a directory or not, e.g. in /alpha/beta", "beta" could be the
+/// name of a directory or is could be a file.  The interpretation here is that
+/// "beta" is the name of a file (although that file could be a directory).
+///
+/// Note: Under Windows, the drive letter is considered to be part of the
+/// directory specification.  Unless this class becomes more widely-used on
+/// Windows, there is no point in adding redundant code.
+///
+/// Name - everthing from the character after the last "/" up to but not
+/// including the last ".".
+///
+/// Extension - everthing from the right-most "." (after the right-most "/") to
+/// the end of the string.  If there is no "." after the last "/", there is
+/// no file extension.
+///
+/// (Note that on Windows, this function will replace all "\" characters
+/// with "/" characters on input strings.)
+///
+/// This class provides functions for extracting the components and for
+/// substituting components.
+
+
+class Filename {
+public:
+
+    /// \brief Constructor
+    Filename(const std::string& name) :
+        full_name_(""), directory_(""), name_(""), extension_("")
+    {
+        setName(name);
+    }
+
+    /// \brief Sets Stored Filename
+    ///
+    /// \param name New name to replaced currently stored name
+    void setName(const std::string& name) {
+        full_name_ = StringUtil::trim(name);
+#ifdef WIN32
+        StringUtil::normalizeSlash(full_name_);
+#endif
+        split(full_name_, directory_, name_, extension_);
+    }
+
+    /// \return Stored Filename
+    std::string fullName() const {
+        return full_name_;
+    }
+
+    /// \return Directory of Given File Name
+    std::string directory() const {
+        return directory_;
+    }
+
+    /// \return Name of Given File Name
+    std::string name() const {
+        return name_;
+    }
+
+    /// \return Extension of Given File Name
+    std::string extension() const {
+        return extension_;
+    }
+
+    /// \brief Expand Name with Default
+    ///
+    /// A default file specified is supplied and used to fill in any missing
+    /// fields.  For example, if the name stored is "/a/b" and the supplied
+    /// name is "c.d", the result is "/a/b.d": the only field missing from the
+    /// stored name is the extension, which is supplied by the default.
+    /// Another example would be to store "a.b" and to supply a default of
+    /// "/c/d/" - the result is "/c/d/a.b".  (Note that if the supplied default
+    /// was "/c/d", the result would be "/c/a.b", even if "/c/d" were actually
+    /// a directory.)
+    ///
+    /// \param defname Default name
+    ///
+    /// \return Name expanded with defname.
+    std::string expandWithDefault(const std::string& defname) const;
+
+    /// \brief Use as Default and Substitute into String
+    ///
+    /// Does essentially the inverse of expand(); that filled in the stored
+    /// name with a default and returned the result.  This treats the stored
+    /// name as the default and uses it to fill in a given name.  In essence,
+    /// the code:
+    /// \code
+    ///       Filename f("/a/b");
+    ///       result = f.expandWithdefault("c.d");
+    /// \endcode
+    /// gives as a result "/a/b.d".  This is the same as:
+    /// \code
+    ///       Filename f("c.d");
+    ///       result = f.useAsDefault("/a/b");
+    /// \endcode
+    ///
+    /// \param name Name to expand
+    ///
+    /// \return Name expanded with stored name
+    std::string useAsDefault(const std::string&) const;
+
+private:
+    /// \brief Split Name into Components
+    ///
+    /// Splits the file name into the directory, name and extension parts.
+    /// The name is assumed to have had back slashes replaced by forward
+    /// slashes (if appropriate).
+    ///
+    /// \param full_name Name to split
+    /// \param directory Returned directory part
+    /// \param name Returned name part
+    /// \param extension Returned extension part
+    void split(const std::string& full_name, std::string& directory,
+       std::string& name, std::string& extension) const;
+
+    // Members
+
+    std::string full_name_;     ///< Given name
+    std::string directory_;     ///< Directory part
+    std::string name_;          ///< Name part
+    std::string extension_;     ///< Extension part
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __FILENAME_H

+ 50 - 0
src/lib/log/log.h

@@ -0,0 +1,50 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __LOG_H
+#define __LOG_H
+
+namespace isc {
+namespace log {
+
+class RootLogger : public Logger
+{
+public:
+
+    /// \brief Return Root Logger
+    ///
+    /// Returns the root logger for the system.  Only one root logger is
+    /// defined and the name corresponds to the 
+    static Log* getRootLogger() {
+        static Log root_logger();
+
+        return &root_logger;
+    }
+
+    /// \brief Set Logger Name
+    ///
+    /// Sets the name of the root logger.  This is dynamic, and 
+
+private:
+    static std::string  root_name_;
+};
+
+}
+}
+
+
+
+#endif // __LOG_H

+ 187 - 0
src/lib/log/logger.cc

@@ -0,0 +1,187 @@
+// Copyright (C) 2010  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 SOFTWAR
+
+// $Id$
+
+#include <iostream>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/xdebuglevel.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Constructor - create a logger as a child of the root logger.  With log4cxx
+// this is assured by naming the logger <parent>.<child>.
+
+Logger::Logger(const std::string& name) : loggerptr_()
+{
+    string root_name = RootLoggerName::getName();
+    if (root_name.empty() || (name == root_name)) {
+        fullname_ = name;
+    }
+    else {
+        fullname_ = root_name + "." + name;
+    }
+    loggerptr_ = log4cxx::Logger::getLogger(fullname_);
+}
+
+// Set the severity for logging.  There is a 1:1 mapping between the logging
+// severity and the log4cxx logging levels, apart from DEBUG.
+//
+// In log4cxx, each of the logging levels (DEBUG, INFO, WARNING etc.) has a
+// numeric value.  The level is set to one of these and any numeric level equal
+// to or above it that is reported.  For example INFO has a value of 20000 and
+// ERROR a value of 40000. So if a message of WARNING severity (= 30000) is
+// logged, it is not logged when the logger's severity level is ERROR (as
+// 30000 !>= 40000).  It is reported if the logger's severity level is set to
+// WARNING (as 30000 >= 30000) or INFO (30000 >= 20000).
+//
+// This gives a simple system for handling different debug levels.  The debug
+// level is a number between 0 and 99, with 0 being least verbose and 99 the
+// most.  To implement this seamlessly, when DEBUG is set, the numeric value
+// of the logging level is actually set to (DEBUG - debug-level).  Similarly
+// messages of level "n" are logged at a logging level of (DEBUG - n).  Thus if
+// the logging level is set to DEBUG and the debug level set to 25, the actual
+// level set is 10000 - 25 = 99975.
+//
+// Attempting to log a debug message of level 26 is an attempt to log a message
+// of level 10000 - 26 = 99974.  As 99974 !>= 99975, it is not logged.  A
+// message of level 25 is, because 99975 >= 99975.
+//
+// The extended set of logging levels is implemented by the XDebugLevel class.
+
+void Logger::setSeverity(Severity severity, int dbglevel) {
+    switch (severity) {
+        case NONE:
+            loggerptr_->setLevel(
+                log4cxx::Level::toLevel(
+                log4cxx::Level::OFF_INT));
+            break;
+
+        case FATAL:
+            loggerptr_->setLevel(
+                log4cxx::Level::toLevel(
+                log4cxx::Level::FATAL_INT));
+            break;
+
+        case ERROR:
+            loggerptr_->setLevel(
+                log4cxx::Level::toLevel(
+                log4cxx::Level::ERROR_INT));
+            break;
+
+        case WARNING:
+            loggerptr_->setLevel(
+                log4cxx::Level::toLevel(
+                log4cxx::Level::WARN_INT));
+            break;
+
+        case INFO:
+            loggerptr_->setLevel(
+                log4cxx::Level::toLevel(
+                log4cxx::Level::INFO_INT));
+            break;
+
+        case DEBUG:
+            loggerptr_->setLevel(
+                log4cxx::XDebugLevel::getExtendedDebug(dbglevel));
+            break;
+
+        // Will get here for DEFAULT or any other value.  This disables the
+        // logger's own severity and it defaults to the severity of the parent
+        // logger.
+        default:
+            loggerptr_->setLevel(0);
+    }
+}
+
+// Convert between numeric log4cxx logging level and BIND-10 logging severity.
+
+Logger::Severity Logger::convertLevel(int value) const {
+
+    // The order is optimised.  This is only likely to be called when testing
+    // for writing debug messages, so the check for DEBUG_INT is first.
+    if (value <= log4cxx::Level::DEBUG_INT) {
+        return (DEBUG);
+    } else if (value <= log4cxx::Level::INFO_INT) {
+        return (INFO);
+    } else if (value <= log4cxx::Level::WARN_INT) {
+        return (WARNING);
+    } else if (value <= log4cxx::Level::ERROR_INT) {
+        return (ERROR);
+    } else if (value <= log4cxx::Level::FATAL_INT) {
+        return (FATAL);
+    } else {
+        return (NONE);
+    }
+}
+
+
+// Return the logging severity associated with this logger.
+
+Logger::Severity Logger::getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+    bool check_parent) const {
+
+    log4cxx::LevelPtr level = ptrlogger->getLevel();
+    if (level == log4cxx::LevelPtr()) {
+
+        // Null level returned, logging should be that of the parent.
+
+        if (check_parent) {
+            log4cxx::LoggerPtr parent = ptrlogger->getParent();
+            if (parent == log4cxx::LoggerPtr()) {
+
+                // No parent, so reached the end of the chain.  Return INFO
+                // severity.
+                return (INFO);
+            }
+            else {
+                return getSeverityCommon(parent, check_parent);
+            }
+        }
+        else {
+            return (DEFAULT);
+        }
+    } else {
+        return convertLevel(level->toInt());
+    }
+}
+// Get the debug level.  This returns 0 unless the severity is DEBUG.
+
+int Logger::getDebugLevel() const {
+
+    log4cxx::LevelPtr level = loggerptr_->getLevel();
+    if (level == log4cxx::LevelPtr()) {
+
+        // Null pointer returned, logging should be that of the parent.
+        return (0);
+        
+    } else {
+        int severity = level->toInt();
+        if (severity <= log4cxx::Level::DEBUG_INT) {
+            return (log4cxx::Level::DEBUG_INT - severity);
+        }
+        else {
+            return (0);
+        }
+    }
+             
+}
+
+} // namespace log
+} // namespace isc

+ 490 - 0
src/lib/log/logger.h

@@ -0,0 +1,490 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __LOGGER_H
+#define __LOGGER_H
+
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include <log4cxx/logger.h>
+
+#include <log/dbglevels.h>
+
+namespace isc {
+namespace log {
+
+class Logger {
+public:
+
+    typedef int MessageCode;    ///< Type of the message code
+
+    /// \brief Severity Levels
+    typedef enum {
+        DEFAULT,    // Default to logging level of parent
+        DEBUG,
+        INFO,
+        WARNING,
+        ERROR,
+        CRITICAL,
+        NONE,       // Disable logging
+        INFORMATION = INFO,
+        WARN = WARNING,
+        FATAL = CRITICAL
+    } Severity;
+
+    /// \brief Return a logger of a given name
+    ///
+    /// Returns a logger with the specified name. 
+    ///
+    /// \param name Name of the logger.  Unless specified as a root logger
+    /// (with a call to setRootLoggerName), the returned logger is a child
+    /// of the root logger.
+    ///
+    /// \return Pointer to Logger object
+    // static Logger* getLogger(const char* name) {}
+
+    /// \brief Set Root Logger Name
+    ///
+    /// One of the first calls in the program, this sets the name of the
+    /// root logger.  (The name appears in logging messages.)
+    ///
+    /// \param name Name of the root logger.
+    // static void setRootLoggerName(const char* name);
+
+    /// \brief Constructor
+    ///
+    /// Creates/attaches to a logger of a specific name.
+    ///
+    /// \param name Name of the logger.  If the name is that of the root name,
+    /// this creates an instance of the root logger; otherwise it creates a
+    /// chold of the root logger.
+    Logger(const std::string& name);
+
+    /// \brief Get Name of Logger
+    ///
+    /// \return The full name of the logger (including the root name)
+    virtual std::string getName() const {
+        return loggerptr_->getName();
+    }
+
+    /// \brief Set Severity Level for Logger
+    ///
+    /// Sets the level at which this logger will log messages.  If none is set,
+    /// the level is inherited from the parent.
+    ///
+    /// \param severity Severity level to log
+    /// \param dbglevel If the severity is DEBUG, this is the debug level.
+    /// This can be in the range 1 to 100 and controls the verbosity.  A value
+    /// outside these limits is silently coerced to the nearest boundary.
+    virtual void setSeverity(Severity severity, int dbglevel = 1);
+
+    /// \brief Get Severity Level for Logger
+    ///
+    /// \return The current logging level of this logger.  In most cases though,
+    /// the effective logging level is what is required.
+    virtual Severity getSeverity() const {
+        return getSeverityCommon(loggerptr_, false);
+    }
+
+    /// \brief Get Effective Severity Level for Logger
+    ///
+    /// \return The effective severity level of the logger.  This is the same
+    /// as getSeverity() if the logger has a severity level set, but otherwise
+    /// is the severity of the parent.
+    virtual Severity getEffectiveSeverity() const {
+        return getSeverityCommon(loggerptr_, true);
+    }
+
+    /// \brief Return DEBUG Level
+    ///
+    /// \return Current setting of debug level.  This is returned regardless of
+    /// whether the 
+    virtual int getDebugLevel() const;
+
+    /// \brief Returns if Debug Message Should Be Output
+    ///
+    /// \param dbglevel Level for which debugging is checked.  Debugging is
+    /// enabled only if the logger has DEBUG enabled and if the dbglevel
+    /// checked is less than or equal to the debug level set for the logger.
+    virtual bool
+    isDebugEnabled(int dbglevel = MIN_DEBUG_LEVEL) const {
+        return (loggerptr_->getEffectiveLevel()->toInt() <=
+            (log4cxx::Level::DEBUG_INT - dbglevel));
+    }
+
+    /// \brief Is INFO Enabled?
+    virtual bool isInfoEnabled() const {
+        return (loggerptr_->isInfoEnabled());
+    }
+
+    /// \brief Is WARNING Enabled?
+    virtual bool isWarnEnabled() const {
+        return (loggerptr_->isWarnEnabled());
+    }
+
+    /// \brief Is WARNING Enabled?
+    virtual bool isWarningEnabled() const {
+        return (loggerptr_->isWarnEnabled());
+    }
+
+    /// \brief Is ERROR Enabled?
+    virtual bool isErrorEnabled() const {
+        return (loggerptr_->isErrorEnabled());
+    }
+
+    /// \brief Is CRITICAL Enabled?
+    virtual bool isCriticalEnabled() const {
+        return (loggerptr_->isFatalEnabled());
+    }
+
+    /// \brief Is FATAL Enabled?
+    ///
+    /// FATAL is a synonym for CRITICAL.
+    virtual bool isFatalEnabled() const {
+        return (loggerptr_->isFatalEnabled());
+    }
+/*
+    /// \brief Add Appender
+    ///
+    /// Adds an appender to the list of appenders for this logger.  The
+    /// appender is assumed to have an independent existence so although
+    /// a pointer to the appender is added here, the logger does not
+    /// assume responsibility for its destruction.
+    ///
+    /// \param appender Pointer to the appender that should be added.
+    /// If the appender is already added to this logger, a duplicate
+    /// is not added.
+    ///
+    /// \return true if the logger was added, false if it was already in the
+    /// list of appenders for this logger.
+    virtual bool addAppender(AbstractAppender* appender);
+
+    /// \brief Remove Appender
+    ///
+    /// Removes the appender from the list of appenders for this logger.
+    /// 
+    /// \param appender Pointer to the appender that should be removed.
+    ///
+    /// \return true if the appender was removed, false if it could not be
+    /// found in the list.
+    virtual bool removeAppender(AbstractAppender* appender);
+
+    /// \brief Get Effective Level for Logger
+    ///
+    /// Gets the current effective logging level.  If the current logger does
+    /// not have a level set, the inheritance tree is traversed until a level
+    /// is found.
+    virtual Level getEffectiveLevel() const;
+    /// \brief Debug Messages
+    ///
+    /// A set of functions that control the output of the message and up to
+    /// four parameters.
+    void debugCommon(MessageCode code, std::string arg);
+
+    template <typename T1>
+    void debug(Level level, MessageCode code, T1 arg1) {
+        if (shouldOutputDebug(level)) {
+            debugCommon(code,
+                boost::lexical_cast<std::string>(arg1)
+            );
+        }
+    }
+
+    template <typename T1, typename T2>
+    void debug(MessageCode code, T1 arg1, T2 arg2) {
+        if (shouldOutputDebug(level)) {
+            debugCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3>
+    void debug(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+        if (shouldOutputDebug(level)) {
+            debugCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3, typename T4>
+    void debug(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+        if (shouldOutputDebug(level)) {
+            debugCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg4)
+            );
+        }
+    }
+
+    /// \brief Informational Messages
+    ///
+    /// A set of functions that control the output of the message and up to
+    /// four parameters.
+    void infoCommon(MessageCode code, std::string arg);
+
+    template <typename T1>
+    void info(MessageCode code, T1 arg1) {
+        if (isInfoEnabled()) {
+            infoCommon(code,
+                boost::lexical_cast<std::string>(arg1)
+            );
+        }
+    }
+
+    template <typename T1, typename T2>
+    void info(MessageCode code, T1 arg1, T2 arg2) {
+        if (isInfoEnabled()) {
+            infoCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3>
+    void info(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+        if (isInfoEnabled()) {
+            infoCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3, typename T4>
+    void info(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+        if (isInfoEnabled()) {
+            infoCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg4)
+            );
+        }
+    }
+
+    /// \brief Warning Messages
+    ///
+    /// A set of functions that control the output of the message and up to
+    /// four parameters.
+    void warnCommon(MessageCode code, std::string arg);
+
+    template <typename T1>
+    void warn(MessageCode code, T1 arg1) {
+        if (isWarnEnabled()) {
+            warnCommon(code,
+                boost::lexical_cast<std::string>(arg1)
+            );
+        }
+    }
+
+    template <typename T1, typename T2>
+    void warn(MessageCode code, T1 arg1, T2 arg2) {
+        if (isWarnEnabled()) {
+            warnCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3>
+    void warn(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+        if (isWarnEnabled()) {
+            warnCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3, typename T4>
+    void warn(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+        if (isWarnEnabled()) {
+            warnCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg4)
+            );
+        }
+    }
+
+    /// \brief Error Messages
+    ///
+    /// A set of functions that control the output of the message and up to
+    /// four parameters.
+    void errorCommon(MessageCode code, std::string arg);
+
+    template <typename T1>
+    void error(MessageCode code, T1 arg1) {
+        if (isErrorEnabled()) {
+            errorCommon(code,
+                boost::lexical_cast<std::string>(arg1)
+            );
+        }
+    }
+
+    template <typename T1, typename T2>
+    void error(MessageCode code, T1 arg1, T2 arg2) {
+        if (isErrorEnabled()) {
+            errorCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3>
+    void error(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+        if (isErrorEnabled()) {
+            errorCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3, typename T4>
+    void error(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+        if (isErrorEnabled()) {
+            errorCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg4)
+            );
+        }
+    }
+
+    /// \brief Critical Messages
+    ///
+    /// A set of functions that control the output of the message and up to
+    /// four parameters.
+    void criticalCommon(MessageCode code, std::string arg);
+
+    template <typename T1>
+    void critical(MessageCode code, T1 arg1) {
+        if (isCriticalEnabled()) {
+            criticalCommon(code,
+                boost::lexical_cast<std::string>(arg1)
+            );
+        }
+    }
+
+    template <typename T1, typename T2>
+    void critical(MessageCode code, T1 arg1, T2 arg2) {
+        if (isCriticalEnabled()) {
+            criticalCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3>
+    void critical(MessageCode code, T1 arg1, T2 arg2, T3 arg3) {
+        if (isCriticalEnabled()) {
+            criticalCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3)
+            );
+        }
+    }
+
+    template <typename T1, typename T2, typename T3, typename T4>
+    void critical(MessageCode code, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
+        if (isCriticalEnabled()) {
+            errorCommon(code,
+                boost::lexical_cast<std::string>(arg1) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg2) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg3) + std::string('\0') +
+                boost::lexical_cast<std::string>(arg4)
+            );
+        }
+    }
+*/
+
+protected:
+
+    /// \brief Equality
+    ///
+    /// Check if two instances of this logger refer to the same stream.
+    /// (This method is principally for testing.)
+    ///
+    /// \return true if the logger objects are instances of the same logger.
+    bool operator==(const Logger& other) const {
+        return (loggerptr_ == other.loggerptr_);
+    }
+
+    /// \brief Logger Initialized
+    ///
+    /// Check that the logger has been properly initialized.  (This method
+    /// is principally for testing.)
+    ///
+    /// \return true if this logger object has been initialized.
+    bool isInitialized() const {
+        return (loggerptr_ != log4cxx::LoggerPtr());
+    }
+
+    /// \brief Get Severity Level for Logger
+    ///
+    /// This is common code for getSeverity() and getEffectiveSeverity() -
+    /// it returns the severity of the logger; if not set (and the check_parent)
+    /// flag is set, it searches up the parent-child tree until a severity
+    /// level is found and uses that.
+    ///
+    /// \param ptrlogger Pointer to the log4cxx logger to check.
+    /// \param check_parent true to search up the tree, false to return the
+    /// current level.
+    ///
+    /// \return The effective severity level of the logger.  This is the same
+    /// as getSeverity() if the logger has a severity level set, but otherwise
+    /// is the severity of the parent.
+    Logger::Severity getSeverityCommon(const log4cxx::LoggerPtr& ptrlogger,
+        bool check_parent) const;
+
+    /// \brief Convert Between BIND-10 and log4cxx Logging Levels
+    ///
+    /// Converts between the numeric value of the log4cxx logging level
+    /// and the BIND-10 severity level.
+    ///
+    /// \param value log4cxx numeric logging level
+    ///
+    /// \return BIND-10 logging severity
+    Severity convertLevel(int value) const;
+
+private:
+    log4cxx::LoggerPtr  loggerptr_; ///< Pointer to the underlying logger
+    std::string         fullname_;  ///< Full name of this logger
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_H

+ 137 - 0
src/lib/log/message_reader.cc

@@ -0,0 +1,137 @@
+// 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.
+
+// $Id$
+
+#include <log/message_reader.h>
+#include <log/stringutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Virtual destructor.
+MessageReader::~MessageReader() {
+}
+
+// Return error text
+
+string MessageReader::errorText(MessageReader::Status status) {
+    switch (status) {
+        case SUCCESS:
+            return "Success";
+
+        case DUPLPRFX:
+            return "Error, duplicate $PREFIX directive found";
+
+        case PRFXEXTRARG:
+            return "Error, $PREFIX directive has extra arguments";
+
+        case PRFXINVARG:
+            return "Error, $PREFIX directive has an invalid argument";
+
+        case PRFXNOARG:
+            return "Error, $PREFIX directive has no arguments";
+
+        case UNRECDIR:
+            return "Error, unrecognised directive";
+
+        default:
+            return "Unknown message code";
+    }
+}
+
+// Read the file
+
+MessageReader::Status MessageReader::readFile(const string&) {
+    return OPENIN;
+}
+
+// Clear the Message Map
+
+void MessageReader::clear() {
+}
+
+// Parse a line of the file
+
+MessageReader::Status MessageReader::processLine(const string& line) {
+
+    // Get rid of leading and trailing spaces
+    string text = StringUtil::trim(line);
+
+    if (text.empty()) {
+        return SUCCESS;             // Ignore blank lines
+
+    } else if ((text[0] == '#') || (text[0] == '+')) {
+        return SUCCESS;             // Ignore comments or descriptions
+
+    } else if (text[0] == '$') {
+        return directive(text);     // Process directives
+
+    } else {
+        return OPENIN;
+
+    }
+}
+
+// Process directive
+
+MessageReader::Status MessageReader::directive(const std::string& text) {
+
+    static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+
+    // Regardless of what happens, all prefixes will be uppercase (as will
+    // all symbols).
+    string line = text;
+    StringUtil::uppercase(line);
+    vector<string> tokens = StringUtil::tokens(line);
+
+    // Only $PREFIX is recognised so far, so we'll handle it here.
+    if (tokens[0] != string("$PREFIX")) {
+        return UNRECDIR;            // Directive is not prefix
+
+    } else if (tokens.size() < 2) {
+        return PRFXNOARG;           // Does not have an argument
+
+    } else if (tokens.size() > 2) {
+        return PRFXEXTRARG;         // Too many arguments
+
+    }
+
+    // Token is potentially valid providing it only contains alphabetic
+    // and numeric characters (and underscores) and does not start with a
+    // digit.
+    
+    if ((tokens[1].find_first_not_of(valid) != string::npos) ||
+        (std::isdigit(tokens[1][0]))) {
+
+        // Invalid character in string opr it starts with a digit.
+        return PRFXINVARG;
+    }
+
+    // All OK - unless the prefix has already been set.
+
+    if (prefix_.size() != 0) {
+        return DUPLPRFX;
+    }
+
+    // Prefix has not been set, so set it and return success.
+
+    prefix_ = tokens[1];
+    return SUCCESS;
+}
+
+} // namespace log
+} // namespace isc

+ 156 - 0
src/lib/log/message_reader.h

@@ -0,0 +1,156 @@
+// 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.
+
+// $Id$
+
+#ifndef __MESSAGE_READER_H
+#define __MESSAGE_READER_H
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace log {
+
+/// \brief Read Message File
+///
+/// Reads a message file and creates a map of identifier against the text of the
+/// message.  This map can be retrieved for subsequent processing.
+
+class MessageReader {
+public:
+
+    /// \brief Status Returns
+    ///
+    /// It may seem odd that a class devoted to reading logfile messages does
+    /// not have its own set of messages.  The reason is that this class is
+    /// used in both the server and in the message compiler.  The latter is
+    /// a stand-along program used to create message files, so at the time this
+    /// file is compiled, there is nothing to build an associated message file.
+    typedef enum {
+        SUCCESS,                // Success, all OK.
+        DUPLPRFX,               // Error, duplicate prefix found
+        OPENIN,                 // Error openinin input file
+        PRFXEXTRARG,            // Error, $PREFIX directive has extra arguments
+        PRFXINVARG,             // Error, $PREFIX has invalid argument
+        PRFXNOARG,              // Error, $PREFIX directive has no arguments
+        UNRECDIR                // Error, unrecognised directive
+
+    } Status;                   // Status code
+
+    /// \brief Other Types
+    typedef std::map<std::string, std::string>  MessageMap;
+    typedef std::vector<std::string>            MessageDuplicates;
+
+    /// \brief Constructor
+    ///
+    /// Default constructor.  All work is done in the main readFile code (so
+    /// that a status return can be returned instead of needing to throw an
+    /// exception).
+    MessageReader() : messages_(), duplicates_()
+    {}
+
+    /// \brief Virtual Destructor
+    virtual ~MessageReader();
+
+    /// \brief Return Error Text
+    ///
+    /// Returns the message associated with the error code
+    ///
+    /// \param status Status code for which a message is required
+    ///
+    /// \return Text of the error.
+    virtual std::string errorText(Status status);
+
+
+    /// \brief Read File
+    ///
+    /// This is the main method of the class and reads in the file, parses it,
+    /// and stores the result in the message map.
+    ///
+    /// \param file Name of the message file.
+    ///
+    /// \return Status return.  Should be SUCCESS, else an error has occurred.
+    virtual Status readFile(const std::string& file);
+
+
+    /// \brief Clears Message Mapgit find new files
+    ///
+    /// In the event of an instance of the class needing to be reused, this
+    /// method will clear the message map and the list of duplicated.
+    virtual void clear();
+
+
+    /// \brief Process Line
+    ///
+    /// Parses a text line and adds it to the message map.  Although this is
+    /// for use in readFile, it can also be used to add individual messages
+    /// to the message map.
+    ///
+    /// \param line Line of text to process
+    ///
+    /// \return Status return
+    virtual Status processLine(const std::string& line);
+
+
+    /// \brief Return Message Map
+    ///
+    /// Returns the message map.
+    ///
+    /// \return Returns a copy of the internal map.
+    /// TODO: Usse a reference?
+    virtual MessageMap getMessageMap() const {
+        return messages_;
+    }
+
+    /// \brief Return Message Duplicates
+    ///
+    /// Returns a copy of the duplicates vector.
+    ///
+    /// \return Copy of the duplicates vector
+    virtual MessageDuplicates getMessageDuplicates() {
+        return duplicates_;
+    }
+
+    /// \brief Get Prefix
+    ///
+    /// \return Argument to the $PREFIX directive (if present)
+    virtual std::string getPrefix() const {
+        return prefix_;
+    }
+
+private:
+
+    /// \brief Handle Directive
+    ///
+    /// Passed a line starting with a "$", this handles the processing of
+    /// directives.
+    ///
+    /// \param line Line of text that starts with "$",
+    ///
+    /// \return Status return code.  NORMAL implies success
+    Status directive(const std::string& line);
+
+    /// Attributes
+    MessageMap          messages_;      // Message map
+    MessageDuplicates   duplicates_;    // Duplicate messages
+    std::string         prefix_;        // Input of $PREFIX statement
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_READER_H

+ 26 - 0
src/lib/log/root_logger_name.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <string>
+#include <root_logger_name.h>
+
+namespace isc {
+namespace log {
+
+std::string RootLoggerName::name_("");
+
+}
+}

+ 57 - 0
src/lib/log/root_logger_name.h

@@ -0,0 +1,57 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __ROOT_LOGGER_NAME_H
+#define __ROOT_LOGGER_NAME_H
+
+#include <string>
+
+/// \brief Define Name of Root Logger
+///
+/// In the log4cxx system, the root logger is ".".  The definition for the
+/// BIND-10 system is that the root logger of a program has the name of the
+/// program.  This (trivial) class stores the name of the program in a
+/// location accessible to the logger classes.
+
+namespace isc {
+namespace log {
+
+class RootLoggerName {
+public:
+
+    /// \brief Set Root Logger Name
+    ///
+    /// \param name Name of the root logger.  This should be the program
+    /// name.
+    static void setName(const std::string& name) {
+        name_ = name;
+    }
+
+    /// \brief Get Root Logger Name
+    ///
+    /// \return Name of the root logger.
+    static std::string getName() {
+        return name_;
+    }
+    
+private:
+    static std::string name_;      ///< Name of the root logger
+};
+
+}
+}
+
+#endif // __ROOT_LOGGER_NAME_H

+ 83 - 0
src/lib/log/stringutil.cc

@@ -0,0 +1,83 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <iostream>
+
+#include <string.h>
+#include <stringutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Trim String
+
+string StringUtil::trim(const string& instring) {
+    static const char* blanks = " \t\n";
+
+    string retstring = "";
+    if (! instring.empty()) {
+
+        // Search for first non-blank character in the string
+        size_t first = instring.find_first_not_of(blanks);
+        if (first != string::npos) {
+
+            // String not all blanks, so look for last character
+            size_t last = instring.find_last_not_of(blanks);
+
+            // Extract the trimmed substring
+            retstring = instring.substr(first, (last - first + 1));
+        }
+    }
+
+    return retstring;
+}
+
+// Tokenise string.  As noted in the header, this is locally written to avoid
+// another dependency on a Boost library.
+
+vector<string> StringUtil::tokens(const std::string text,
+    const std::string& delim)
+{
+    vector<string> result;
+
+    // Search for the first non-delimiter character
+    size_t start = text.find_first_not_of(delim);
+    while (start != string::npos) {
+
+        // Non-delimiter found, look for next delimiter
+        size_t end = text.find_first_of(delim, start);
+        if (end != string::npos) {
+
+            // Delimiter found, so extract string & search for start of next
+            // non-delimiter segment.
+            result.push_back(text.substr(start, (end - start)));
+            start = text.find_first_not_of(delim, end);
+
+        } else {
+
+            // End of string found, extract rest of string and flag to exit
+            result.push_back(text.substr(start));
+            start = string::npos;
+        }
+    }
+
+    return result;
+}
+
+} // namespace log
+} // namespace isc

+ 129 - 0
src/lib/log/stringutil.h

@@ -0,0 +1,129 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __STRINGUTIL_H
+#define __STRINGUTIL_H
+
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace log {
+
+/// \brief A Set of C++ Utilities for Manipulating Strings
+
+class StringUtil {
+public:
+    /// \brief Normalize Backslash
+    ///
+    /// Only active on Windows, this replaces all "\" in a string with "/"
+    /// and returns the result.  On other systems it is a no-op.  Note that
+    /// Windows does recognise file names with the "\" replaced by "/" (at
+    /// least in system calls, if not the command line).
+    ///
+    /// \param name Name to be substituted
+    static void normalizeSlash(std::string& name) {
+        if (! name.empty()) {
+            size_t pos = 0;
+            while ((pos = name.find('\\', pos)) != std::string::npos) {
+                name[pos] = '/';
+            }
+        }
+    }
+
+    /// \brief Trim Leading and Trailing Spaces
+    ///
+    /// Returns a copy of the input string but with any leading or trailing
+    /// spaces or tabs removed.
+    ///
+    /// \param instring Input string to modify
+    ///
+    /// \return String with leading and trailing spaces removed
+    static std::string trim(const std::string& instring);
+
+    /// \brief Split String into Tokens
+    ///
+    /// Splits a string into tokens (the tokens being delimited by one or more
+    /// of the delimiter characters) and returns the tokens in a vector array.
+    /// Note that adjacent delimiters are considered to be a single delimiter.
+    ///
+    /// We could use Boost for this, but this (simple) function eliminates one
+    /// dependency in the code.
+    ///
+    /// \param text String to be split.  Passed by value as the internal copy
+    /// is altered during the processing.
+    /// \param delim Delimiter characters
+    ///
+    /// \return Vector of tokens.
+    static std::vector<std::string> tokens(const std::string text,
+            const std::string& delim = std::string(" \t\n"));
+
+    /// \brief Uppercase Character
+    ///
+    /// Needed to pass as an argument to transform() in uppercase(), as the
+    /// function std::toupper() takes an "int" as its argument and the template
+    /// expansion mechanism gets confused.
+    ///
+    /// \param chr Character to be upper-cased.
+    ///
+    /// \return Uppercase version of the argument
+    static char toUpper(char chr) {
+        return static_cast<char>(std::toupper(static_cast<int>(chr)));
+    }
+
+    /// \brief Uppercase String
+    ///
+    /// A convenience function to uppercase a string
+    ///
+    /// \param text String to be upper-cased.
+    static void uppercase(std::string& text) {
+        std::transform(text.begin(), text.end(), text.begin(),
+            StringUtil::toUpper);
+    }
+
+    /// \brief Lowercase Character
+    ///
+    /// Needed to pass as an argument to transform() in lowercase(), as the
+    /// function std::tolower() takes an "int" as its argument and the template
+    /// expansion mechanism gets confused.
+    ///
+    /// \param chr Character to be lower-cased.
+    ///
+    /// \return Lowercase version of the argument
+    static char toLower(char chr) {
+        return static_cast<char>(std::tolower(static_cast<int>(chr)));
+    }
+
+    /// \brief Lowercase String
+    ///
+    /// A convenience function to lowercase a string
+    ///
+    /// \param text String to be lower-cased.
+    static void lowercase(std::string& text) {
+        std::transform(text.begin(), text.end(), text.begin(),
+            StringUtil::toLower);
+    }
+
+};
+
+} // namespace log
+} // namespace isc
+
+
+
+#endif // __STRINGUTIL_H

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

@@ -0,0 +1,31 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES  = root_logger_name_unittest.cc
+run_unittests_SOURCES += filename_unittest.cc
+run_unittests_SOURCES += logger_unittest.cc
+run_unittests_SOURCES += message_reader_unittest.cc
+run_unittests_SOURCES += stringutil_unittest.cc
+run_unittests_SOURCES += xdebuglevel_unittest.cc
+run_unittests_SOURCES += run_unittests.cc
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD  = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+run_unittests_LDADD += -llog4cxx
+endif
+
+noinst_PROGRAMS = $(TESTS)

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

@@ -0,0 +1,146 @@
+// Copyright (C) 2010  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.
+
+// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/filename.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class FilenameTest : public ::testing::Test {
+protected:
+    FilenameTest()
+    {
+    }
+};
+
+
+// Check that the name can be changed
+
+TEST_F(FilenameTest, SetName) {
+    Filename fname("/a/b/c.d");
+    EXPECT_EQ("/a/b/c.d", fname.fullName());
+
+    fname.setName("test.txt");
+    EXPECT_EQ("test.txt", fname.fullName());
+}
+
+
+// Check that the components are split correctly.  This is a check of the
+// private member split() method.
+
+TEST_F(FilenameTest, Components) {
+
+    // Complete name
+    Filename fname("/alpha/beta/gamma.delta");
+    EXPECT_EQ("/alpha/beta/", fname.directory());
+    EXPECT_EQ("gamma", fname.name());
+    EXPECT_EQ(".delta", fname.extension());
+
+    // Directory only
+    fname.setName("/gamma/delta/");
+    EXPECT_EQ("/gamma/delta/", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ("", fname.extension());
+
+    // Filename only
+    fname.setName("epsilon");
+    EXPECT_EQ("", fname.directory());
+    EXPECT_EQ("epsilon", fname.name());
+    EXPECT_EQ("", fname.extension());
+
+    // Extension only
+    fname.setName(".zeta");
+    EXPECT_EQ("", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ(".zeta", fname.extension());
+
+    // Missing directory
+    fname.setName("eta.theta");
+    EXPECT_EQ("", fname.directory());
+    EXPECT_EQ("eta", fname.name());
+    EXPECT_EQ(".theta", fname.extension());
+
+    // Missing filename
+    fname.setName("/iota/.kappa");
+    EXPECT_EQ("/iota/", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ(".kappa", fname.extension());
+
+    // Missing extension
+    fname.setName("lambda/mu/nu");
+    EXPECT_EQ("lambda/mu/", fname.directory());
+    EXPECT_EQ("nu", fname.name());
+    EXPECT_EQ("", fname.extension());
+
+    // Empty string
+    fname.setName("");
+    EXPECT_EQ("", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ("", fname.extension());
+
+    // ... and finally check that the decomposition can occur in the presence
+    // of leading and trailing spaces
+    fname.setName("  lambda/mu/nu\t  ");
+    EXPECT_EQ("lambda/mu/", fname.directory());
+    EXPECT_EQ("nu", fname.name());
+    EXPECT_EQ("", fname.extension());
+}
+
+// Check that the expansion with a default works.
+
+TEST_F(FilenameTest, ExpandWithDefault) {
+    Filename fname("a.b");
+
+    // These tests also check that the trimming of the default component is
+    // done properly.
+    EXPECT_EQ("/c/d/a.b", fname.expandWithDefault(" /c/d/  "));
+    EXPECT_EQ("/c/d/a.b", fname.expandWithDefault("/c/d/e.f"));
+    EXPECT_EQ("a.b", fname.expandWithDefault("e.f"));
+
+    fname.setName("/a/b/c");
+    EXPECT_EQ("/a/b/c.d", fname.expandWithDefault(".d"));
+    EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("x.d"));
+    EXPECT_EQ("/a/b/c.d", fname.expandWithDefault("/s/t/u.d"));
+    EXPECT_EQ("/a/b/c", fname.expandWithDefault("/s/t/u"));
+
+    fname.setName(".h");
+    EXPECT_EQ("/a/b/c.h", fname.expandWithDefault("/a/b/c.msg"));
+}
+
+// Check that we can use this as a default in expanding a filename
+
+TEST_F(FilenameTest, UseAsDefault) {
+
+    Filename fname("a.b");
+
+    // These tests also check that the trimming of the default component is
+    // done properly.
+    EXPECT_EQ("/c/d/a.b", fname.useAsDefault(" /c/d/  "));
+    EXPECT_EQ("/c/d/e.f", fname.useAsDefault("/c/d/e.f"));
+    EXPECT_EQ("e.f", fname.useAsDefault("e.f"));
+
+    fname.setName("/a/b/c");
+    EXPECT_EQ("/a/b/c.d", fname.useAsDefault(".d"));
+    EXPECT_EQ("/a/b/x.d", fname.useAsDefault("x.d"));
+    EXPECT_EQ("/s/t/u.d", fname.useAsDefault("/s/t/u.d"));
+    EXPECT_EQ("/s/t/u", fname.useAsDefault("/s/t/u"));
+    EXPECT_EQ("/a/b/c", fname.useAsDefault(""));
+}

+ 383 - 0
src/lib/log/tests/logger_unittest.cc

@@ -0,0 +1,383 @@
+// Copyright (C) 2010  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.
+
+// $Id: $
+
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+
+using namespace isc;
+using namespace isc::log;
+
+namespace isc {
+namespace log {
+
+/// \brief Test Logger
+///
+/// This logger is a subclass of the logger class under test, but makes
+/// protected methods public (for testing)
+
+class TestLogger : public Logger {
+public:
+    /// \brief constructor
+    TestLogger(const std::string& name) : Logger(name)
+    {}
+
+    /// \brief Logger Equality
+    bool operator==(const TestLogger& other) {
+        return Logger::operator==(other);
+    }
+
+    /// \brief Logger is Null
+    bool isInitialized() const {
+        return Logger::isInitialized();
+    }
+
+    /// \brief Conversion Between log4cxx Number and BIND-10 Severity
+    Severity convertLevel(int value) {
+        return Logger::convertLevel(value);
+    }
+};
+
+} // namespace log
+} // namespace isc
+
+
+class LoggerTest : public ::testing::Test {
+protected:
+    LoggerTest()
+    {
+    }
+};
+
+
+// Checks that the logger is named correctly.
+
+TEST_F(LoggerTest, Name) {
+
+    // Create a logger
+    RootLoggerName::setName("test1");
+    Logger logger("alpha");
+
+    // ... and check the name
+    EXPECT_EQ(std::string("test1.alpha"), logger.getName());
+}
+
+// This test attempts to get two instances of a logger with the same name
+// and checks that they are in fact the same logger.
+
+TEST_F(LoggerTest, GetLogger) {
+
+    // Set the root logger name (not strictly needed, but this will be the
+    // case in the program(.
+    RootLoggerName::setName("test2");
+
+    const std::string name1 = "alpha";
+    const std::string name2 = "beta";
+
+    // Instantiate two loggers that should be the same
+    TestLogger logger1(name1);
+    TestLogger logger2(name1);
+
+    // And check they are equal and non-null
+    EXPECT_TRUE(logger1.isInitialized());
+    EXPECT_TRUE(logger2.isInitialized());
+    EXPECT_TRUE(logger1 == logger2);
+
+    // Instantiate another logger with another name and check that it
+    // is different to the previously instantiated ones.
+    TestLogger logger3(name2);
+    EXPECT_TRUE(logger3.isInitialized());
+    EXPECT_FALSE(logger1 == logger3);
+}
+
+// Test the number to severity conversion function
+
+TEST_F(LoggerTest, ConvertLevel) {
+
+    // Create a logger
+    RootLoggerName::setName("test3");
+    TestLogger logger("alpha");
+
+    // Basic 1:1
+    EXPECT_EQ(Logger::DEBUG, logger.convertLevel(log4cxx::Level::DEBUG_INT));
+    EXPECT_EQ(Logger::INFO, logger.convertLevel(log4cxx::Level::INFO_INT));
+    EXPECT_EQ(Logger::WARN, logger.convertLevel(log4cxx::Level::WARN_INT));
+    EXPECT_EQ(Logger::WARNING, logger.convertLevel(log4cxx::Level::WARN_INT));
+    EXPECT_EQ(Logger::ERROR, logger.convertLevel(log4cxx::Level::ERROR_INT));
+    EXPECT_EQ(Logger::FATAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
+    EXPECT_EQ(Logger::CRITICAL, logger.convertLevel(log4cxx::Level::FATAL_INT));
+    EXPECT_EQ(Logger::NONE, logger.convertLevel(log4cxx::Level::OFF_INT));
+
+    // Now some debug levels
+    EXPECT_EQ(Logger::DEBUG,
+        logger.convertLevel(log4cxx::Level::DEBUG_INT - 1));
+    EXPECT_EQ(Logger::DEBUG,
+        logger.convertLevel(log4cxx::Level::DEBUG_INT - MAX_DEBUG_LEVEL));
+    EXPECT_EQ(Logger::DEBUG,
+        logger.convertLevel(log4cxx::Level::DEBUG_INT - 2 * MAX_DEBUG_LEVEL));
+}
+
+// Check that the logger levels are get set properly.
+
+TEST_F(LoggerTest, Severity) {
+
+    // Create a logger
+    RootLoggerName::setName("test3");
+    Logger logger("alpha");
+
+    // Now check the levels
+    logger.setSeverity(Logger::NONE);
+    EXPECT_EQ(Logger::NONE, logger.getSeverity());
+
+    logger.setSeverity(Logger::CRITICAL);
+    EXPECT_EQ(Logger::CRITICAL, logger.getSeverity());
+
+    logger.setSeverity(Logger::ERROR);
+    EXPECT_EQ(Logger::ERROR, logger.getSeverity());
+
+    logger.setSeverity(Logger::WARNING);
+    EXPECT_EQ(Logger::WARNING, logger.getSeverity());
+
+    logger.setSeverity(Logger::INFO);
+    EXPECT_EQ(Logger::INFO, logger.getSeverity());
+
+    logger.setSeverity(Logger::DEBUG);
+    EXPECT_EQ(Logger::DEBUG, logger.getSeverity());
+
+    logger.setSeverity(Logger::DEFAULT);
+    EXPECT_EQ(Logger::DEFAULT, logger.getSeverity());
+}
+
+// Check that the debug level is set correctly.
+
+TEST_F(LoggerTest, DebugLevels) {
+
+    // Create a logger
+    RootLoggerName::setName("test4");
+    Logger logger("alpha");
+
+    // Debug level should be 0 if not at debug severity
+    logger.setSeverity(Logger::NONE, 20);
+    EXPECT_EQ(0, logger.getDebugLevel());
+
+    logger.setSeverity(Logger::INFO, 42);
+    EXPECT_EQ(0, logger.getDebugLevel());
+
+    // Should be the value set if the severity is set to DEBUG though.
+    logger.setSeverity(Logger::DEBUG, 32);
+    EXPECT_EQ(32, logger.getDebugLevel());
+
+    logger.setSeverity(Logger::DEBUG, 97);
+    EXPECT_EQ(97, logger.getDebugLevel());
+
+    // Try the limits
+    logger.setSeverity(Logger::DEBUG, -1);
+    EXPECT_EQ(0, logger.getDebugLevel());
+
+    logger.setSeverity(Logger::DEBUG, 0);
+    EXPECT_EQ(0, logger.getDebugLevel());
+
+    logger.setSeverity(Logger::DEBUG, 1);
+    EXPECT_EQ(1, logger.getDebugLevel());
+
+    logger.setSeverity(Logger::DEBUG, 98);
+    EXPECT_EQ(98, logger.getDebugLevel());
+
+    logger.setSeverity(Logger::DEBUG, 99);
+    EXPECT_EQ(99, logger.getDebugLevel());
+
+    logger.setSeverity(Logger::DEBUG, 100);
+    EXPECT_EQ(99, logger.getDebugLevel());
+}
+
+// Check that changing the parent and child severity does not affect the
+// other.
+
+TEST_F(LoggerTest, SeverityInheritance) {
+
+    // Create to loggers.  We cheat here as we know that the underlying
+    // implementation (in this case log4cxx) will set a parent-child
+    // relationship if the loggers are named <parent> and <parent>.<child>.
+
+    RootLoggerName::setName("test5");
+    Logger parent("alpha");
+    Logger child("alpha.beta");
+
+    // By default, newly created loggers should have a level of DEFAULT
+    // (i.e. default to parent)
+    EXPECT_EQ(Logger::DEFAULT, parent.getSeverity());
+    EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
+
+    // Set the severity of the child to something other than the default -
+    // check it changes and that of the parent does not.
+    child.setSeverity(Logger::INFO);
+    EXPECT_EQ(Logger::DEFAULT, parent.getSeverity());
+    EXPECT_EQ(Logger::INFO, child.getSeverity());
+
+    // Reset the child severity and set that of the parent
+    child.setSeverity(Logger::DEFAULT);
+    EXPECT_EQ(Logger::DEFAULT, parent.getSeverity());
+    EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
+    parent.setSeverity(Logger::WARN);
+    EXPECT_EQ(Logger::WARN, parent.getSeverity());
+    EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
+}
+
+// Check that severity is inherited.
+
+TEST_F(LoggerTest, EffectiveSeverityInheritance) {
+
+    // Create to loggers.  We cheat here as we know that the underlying
+    // implementation (in this case log4cxx) will set a parent-child
+    // relationship if the loggers are named <parent> and <parent>.<child>.
+
+    RootLoggerName::setName("test6");
+    Logger parent("test6");
+    Logger child("test6.beta");
+
+    // By default, newly created loggers should have a level of DEFAULT
+    // (i.e. default to parent) and the root should have a default severity
+    // of INFO.  However, the latter is only enforced when created by the
+    // RootLogger class, so explicitly set it for the parent for now.
+    parent.setSeverity(Logger::INFO);
+    EXPECT_EQ(Logger::INFO, parent.getEffectiveSeverity());
+
+    EXPECT_EQ(Logger::DEFAULT, child.getSeverity());
+    EXPECT_EQ(Logger::INFO, child.getEffectiveSeverity());
+
+    // Set the severity of the child to something other than the default -
+    // check it changes and that of the parent does not.
+    child.setSeverity(Logger::FATAL);
+    EXPECT_EQ(Logger::INFO, parent.getEffectiveSeverity());
+    EXPECT_EQ(Logger::FATAL, child.getEffectiveSeverity());
+
+    // Reset the child severity and check again.
+    child.setSeverity(Logger::DEFAULT);
+    EXPECT_EQ(Logger::INFO, parent.getEffectiveSeverity());
+    EXPECT_EQ(Logger::INFO, child.getEffectiveSeverity());
+
+    // Change the parwnt's severity and check it is reflects in the child.
+    parent.setSeverity(Logger::WARN);
+    EXPECT_EQ(Logger::WARN, parent.getEffectiveSeverity());
+    EXPECT_EQ(Logger::WARN, child.getEffectiveSeverity());
+}
+
+// Test the isXxxxEnabled methods.
+
+TEST_F(LoggerTest, IsXxxEnabled) {
+
+    RootLoggerName::setName("test7");
+    Logger logger("test7");
+
+    logger.setSeverity(Logger::INFO);
+    EXPECT_FALSE(logger.isDebugEnabled());
+    EXPECT_TRUE(logger.isInfoEnabled());
+    EXPECT_TRUE(logger.isWarnEnabled());
+    EXPECT_TRUE(logger.isErrorEnabled());
+    EXPECT_TRUE(logger.isFatalEnabled());
+
+    logger.setSeverity(Logger::WARN);
+    EXPECT_FALSE(logger.isDebugEnabled());
+    EXPECT_FALSE(logger.isInfoEnabled());
+    EXPECT_TRUE(logger.isWarnEnabled());
+    EXPECT_TRUE(logger.isErrorEnabled());
+    EXPECT_TRUE(logger.isFatalEnabled());
+
+    logger.setSeverity(Logger::ERROR);
+    EXPECT_FALSE(logger.isDebugEnabled());
+    EXPECT_FALSE(logger.isInfoEnabled());
+    EXPECT_FALSE(logger.isWarnEnabled());
+    EXPECT_TRUE(logger.isErrorEnabled());
+    EXPECT_TRUE(logger.isFatalEnabled());
+
+    logger.setSeverity(Logger::FATAL);
+    EXPECT_FALSE(logger.isDebugEnabled());
+    EXPECT_FALSE(logger.isInfoEnabled());
+    EXPECT_FALSE(logger.isWarnEnabled());
+    EXPECT_FALSE(logger.isErrorEnabled());
+    EXPECT_TRUE(logger.isFatalEnabled());
+
+    // Check various debug levels
+
+    logger.setSeverity(Logger::DEBUG);
+    EXPECT_TRUE(logger.isDebugEnabled());
+    EXPECT_TRUE(logger.isInfoEnabled());
+    EXPECT_TRUE(logger.isWarnEnabled());
+    EXPECT_TRUE(logger.isErrorEnabled());
+    EXPECT_TRUE(logger.isFatalEnabled());
+
+    logger.setSeverity(Logger::DEBUG, 45);
+    EXPECT_TRUE(logger.isDebugEnabled());
+    EXPECT_TRUE(logger.isInfoEnabled());
+    EXPECT_TRUE(logger.isWarnEnabled());
+    EXPECT_TRUE(logger.isErrorEnabled());
+    EXPECT_TRUE(logger.isFatalEnabled());
+
+    // Create a child logger with no severity set, and check that it reflects
+    // the severity of the parent logger.
+
+    Logger child("test7.child");
+    logger.setSeverity(Logger::FATAL);
+    EXPECT_FALSE(child.isDebugEnabled());
+    EXPECT_FALSE(child.isInfoEnabled());
+    EXPECT_FALSE(child.isWarnEnabled());
+    EXPECT_FALSE(child.isErrorEnabled());
+    EXPECT_TRUE(child.isFatalEnabled());
+
+    logger.setSeverity(Logger::INFO);
+    EXPECT_FALSE(child.isDebugEnabled());
+    EXPECT_TRUE(child.isInfoEnabled());
+    EXPECT_TRUE(child.isWarnEnabled());
+    EXPECT_TRUE(child.isErrorEnabled());
+    EXPECT_TRUE(child.isFatalEnabled());
+}
+
+// Within the Debug level there are 100 debug levels.  Test that we know
+// when to issue a debug message.
+
+TEST_F(LoggerTest, IsDebugEnabledLevel) {
+
+    RootLoggerName::setName("test8");
+    Logger logger("test8");
+
+    int MID_LEVEL = (MIN_DEBUG_LEVEL + MAX_DEBUG_LEVEL) / 2;
+
+    logger.setSeverity(Logger::DEBUG);
+    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+    EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
+    EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+    logger.setSeverity(Logger::DEBUG, MIN_DEBUG_LEVEL);
+    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+    EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL));
+    EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+    logger.setSeverity(Logger::DEBUG, MID_LEVEL);
+    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+    EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL - 1));
+    EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
+    EXPECT_FALSE(logger.isDebugEnabled(MID_LEVEL + 1));
+    EXPECT_FALSE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+
+    logger.setSeverity(Logger::DEBUG, MAX_DEBUG_LEVEL);
+    EXPECT_TRUE(logger.isDebugEnabled(MIN_DEBUG_LEVEL));
+    EXPECT_TRUE(logger.isDebugEnabled(MID_LEVEL));
+    EXPECT_TRUE(logger.isDebugEnabled(MAX_DEBUG_LEVEL));
+}

+ 116 - 0
src/lib/log/tests/message_reader_unittest.cc

@@ -0,0 +1,116 @@
+// Copyright (C) 2010  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.
+
+// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_reader.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class MessageReaderTest : public ::testing::Test {
+protected:
+    MessageReaderTest() : reader_()
+    {
+    }
+
+    MessageReader   reader_;        // Default reader object
+};
+
+
+// Check for a couple of status messages
+
+TEST_F(MessageReaderTest, StatusMessage) {
+    string unknown("Unknown message code");     // Default unknown message
+
+    EXPECT_NE(unknown, reader_.errorText(MessageReader::SUCCESS));
+    EXPECT_NE(unknown, reader_.errorText(MessageReader::DUPLPRFX));
+    EXPECT_NE(unknown, reader_.errorText(MessageReader::PRFXEXTRARG));
+    EXPECT_NE(unknown, reader_.errorText(MessageReader::PRFXINVARG));
+    EXPECT_NE(unknown, reader_.errorText(MessageReader::PRFXNOARG));
+    EXPECT_NE(unknown, reader_.errorText(MessageReader::UNRECDIR));
+}
+
+// Check for parsing blank lines and comments.  These should not add to the map and
+// each parse should return success.
+
+TEST_F(MessageReaderTest, BlanksAndComments) {
+
+    // Ensure that the map is empty
+    MessageReader::MessageMap mmap = reader_.getMessageMap();
+    EXPECT_EQ(0, mmap.size());
+
+    MessageReader::MessageDuplicates mduplicates = reader_.getMessageDuplicates();
+    EXPECT_EQ(0, mduplicates.size());
+
+    // Add a number of blank lines and comments and check that (a) they are parsed
+    // successfully ...
+    MessageReader::Status status = reader_.processLine("");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+    status = reader_.processLine(" ");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+    status = reader_.processLine(" \n ");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+    status = reader_.processLine("# This is a comment");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+    status = reader_.processLine("\t\t # Another comment");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+    status = reader_.processLine("  + A description line");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+    status = reader_.processLine("#+ A comment");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+    status = reader_.processLine("  +# A description line");
+    EXPECT_EQ(MessageReader::SUCCESS, status);
+
+    // ... and (b) nothing gets added to either the map or the duplicates
+    mmap = reader_.getMessageMap();
+    EXPECT_EQ(0, mmap.size());
+
+    mduplicates = reader_.getMessageDuplicates();
+    EXPECT_EQ(0, mduplicates.size());
+}
+
+// Check that it can parse a prefix
+
+TEST_F(MessageReaderTest, Prefix) {
+
+    // Check that no prefix is present
+    EXPECT_EQ(string(""), reader_.getPrefix());
+
+    // Check that a prefix directive with no argument generates an error.
+    EXPECT_EQ(MessageReader::PRFXNOARG, reader_.processLine("$PREFIX"));
+
+    // Check a prefix with multiple arguments is invalid
+    EXPECT_EQ(MessageReader::PRFXEXTRARG, reader_.processLine("$prefix A B"));
+
+    // Prefixes should be alphanumeric (with underscores) and not start
+    // with a number.
+    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix ab[cd"));
+    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix 123"));
+    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix 1ABC"));
+
+    // A valid prefix should be accepted
+    EXPECT_EQ(MessageReader::SUCCESS, reader_.processLine("$PREFIX   dlm__"));
+    EXPECT_EQ(string("DLM__"), reader_.getPrefix());
+
+    // And check that the parser fails on invalid prefixes...
+    EXPECT_EQ(MessageReader::PRFXINVARG, reader_.processLine("$prefix 1ABC"));
+
+    // ... and rejects another valid one
+    EXPECT_EQ(MessageReader::DUPLPRFX, reader_.processLine("$PREFIX ABC"));
+}
+

+ 52 - 0
src/lib/log/tests/root_logger_name_unittest.cc

@@ -0,0 +1,52 @@
+// Copyright (C) 2010  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.
+
+// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/root_logger_name.h>
+
+using namespace isc;
+using namespace isc::log;
+
+class RootLoggerNameTest : public ::testing::Test {
+protected:
+    RootLoggerNameTest()
+    {
+    }
+};
+
+// Check of the (only) functionality of the class.
+
+TEST_F(RootLoggerNameTest, SetGet) {
+    const std::string name1 = "test1";
+    const std::string name2 = "test2";
+
+    // Check that Set/Get works
+    RootLoggerName::setName(name1);
+    EXPECT_EQ(name1, RootLoggerName::getName());
+
+    // We could not test that the root logger name is initialised
+    // correctly (as there is one instance of it and we don't know
+    // when this test will be run) so to check that setName() actually
+    // does change the name, run the test again with a different name.
+    //
+    // (There was always the outside chance that the root logger name
+    // was initialised with name1 and that setName() has no effect.)
+    RootLoggerName::setName(name2);
+    EXPECT_EQ(name2, RootLoggerName::getName());
+}

+ 23 - 0
src/lib/log/tests/run_unittests.cc

@@ -0,0 +1,23 @@
+// Copyright (C) 2009  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.
+
+// $Id: run_unittests.cc 3020 2010-09-26 03:47:26Z jinmei $
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return (RUN_ALL_TESTS());
+}

+ 122 - 0
src/lib/log/tests/stringutil_unittest.cc

@@ -0,0 +1,122 @@
+// Copyright (C) 2010  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.
+
+// $Id: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log/stringutil.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class StringUtilTest : public ::testing::Test {
+protected:
+    StringUtilTest()
+    {
+    }
+};
+
+
+// Check for slash replacement
+
+TEST_F(StringUtilTest, Slash) {
+
+    string instring = "";
+    StringUtil::normalizeSlash(instring);
+    EXPECT_EQ("", instring);
+
+    instring = "C:\\A\\B\\C.D";
+    StringUtil::normalizeSlash(instring);
+    EXPECT_EQ("C:/A/B/C.D", instring);
+
+    instring = "// \\ //";
+    StringUtil::normalizeSlash(instring);
+    EXPECT_EQ("// / //", instring);
+}
+
+// Check that leading and trailing space trimming works
+
+TEST_F(StringUtilTest, Trim) {
+
+    // Empty and full string.
+    EXPECT_EQ("", StringUtil::trim(""));
+    EXPECT_EQ("abcxyz", StringUtil::trim("abcxyz"));
+
+    // Trim right-most blanks
+    EXPECT_EQ("ABC", StringUtil::trim("ABC   "));
+    EXPECT_EQ("ABC", StringUtil::trim("ABC\t\t  \n\t"));
+
+    // Left-most blank trimming
+    EXPECT_EQ("XYZ", StringUtil::trim("  XYZ"));
+    EXPECT_EQ("XYZ", StringUtil::trim("\t\t  \tXYZ"));
+
+    // Right and left, with embedded spaces
+    EXPECT_EQ("MN \t OP", StringUtil::trim("\t\tMN \t OP \t"));
+}
+
+// Check tokenization.  Note that ASSERT_EQ is used to check the size of the
+// returned vector; if not as expected, the following references may be invalid
+// so should not be used.
+
+TEST_F(StringUtilTest, Tokens) {
+    vector<string>  result;
+
+    // Default delimiters
+    result = StringUtil::tokens(" \n ");    // Empty string
+    EXPECT_EQ(0, result.size());
+
+    result = StringUtil::tokens("abc");     // Full string
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+
+    result = StringUtil::tokens("\t xyz \n");
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = StringUtil::tokens("abc\ndef\t\tghi ");
+    ASSERT_EQ(3, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+    EXPECT_EQ(string("ghi"), result[2]);
+
+    // Non-default delimiters
+    result = StringUtil::tokens("alpha/beta/ /gamma//delta/epsilon/", "/");
+    ASSERT_EQ(6, result.size());
+    EXPECT_EQ(string("alpha"), result[0]);
+    EXPECT_EQ(string("beta"), result[1]);
+    EXPECT_EQ(string(" "), result[2]);
+    EXPECT_EQ(string("gamma"), result[3]);
+    EXPECT_EQ(string("delta"), result[4]);
+    EXPECT_EQ(string("epsilon"), result[5]);
+}
+
+// Changing case
+
+TEST_F(StringUtilTest, ChangeCase) {
+    string mixed("abcDEFghiJKLmno123[]{=+--+]}");
+    string upper("ABCDEFGHIJKLMNO123[]{=+--+]}");
+    string lower("abcdefghijklmno123[]{=+--+]}");
+
+    string test = mixed;
+    StringUtil::lowercase(test);
+    EXPECT_EQ(lower, test);
+
+    test = mixed;
+    StringUtil::uppercase(test);
+    EXPECT_EQ(upper, test);
+}

+ 205 - 0
src/lib/log/tests/xdebuglevel_unittest.cc

@@ -0,0 +1,205 @@
+// Copyright (C) 2010  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.
+
+// $Id: $
+
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <log4cxx/level.h>
+#include <log/xdebuglevel.h>
+#include <log/dbglevels.h>
+
+/// \brief XDebugLevel (Debug Extension to Level Class)
+///
+/// The class is an extension of the log4cxx Level class; this set of tests
+/// only test the extensions, they do not test the underlying Level class
+/// itself.
+
+using namespace log4cxx;
+
+class XDebugLevelTest : public ::testing::Test {
+protected:
+    XDebugLevelTest()
+    {
+    }
+};
+
+// Check a basic assertion about the numeric values of the debug levels
+
+TEST_F(XDebugLevelTest, NumericValues) {
+    EXPECT_EQ(XDebugLevel::XDEBUG_MIN_LEVEL_INT, Level::DEBUG_INT);
+    EXPECT_EQ(XDebugLevel::XDEBUG_MAX_LEVEL_INT,
+        Level::DEBUG_INT - MAX_DEBUG_LEVEL);
+
+    // ... and check that assumptions used below - that the debug levels
+    // range from 0 to 99 - are valid.
+    EXPECT_EQ(0, MIN_DEBUG_LEVEL);
+    EXPECT_EQ(99, MAX_DEBUG_LEVEL);
+}
+
+
+// Checks that the main function for generating logging level objects from
+// debug levels is working.
+
+TEST_F(XDebugLevelTest, GetExtendedDebug) {
+
+    // Get a debug level of 0.  This should be the same as the main DEBUG
+    // level.
+    LevelPtr debug0 = XDebugLevel::getExtendedDebug(0);
+    EXPECT_EQ(std::string("DEBUG"), debug0->toString());
+    EXPECT_EQ(Level::DEBUG_INT, debug0->toInt());
+    EXPECT_TRUE(*Level::getDebug() == *debug0);
+
+    // Get an arbitrary debug level in the allowed range.
+    LevelPtr debug32 = XDebugLevel::getExtendedDebug(32);
+    EXPECT_EQ(std::string("DEBUG32"), debug32->toString());
+    EXPECT_TRUE((XDebugLevel::XDEBUG_MIN_LEVEL_INT - 32) == debug32->toInt());
+
+    // Check that a value outside the range gives the nearest level.
+    LevelPtr debug_more = XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL + 1);
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL) == *debug_more);
+
+    LevelPtr debug_less = XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL - 1);
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL) == *debug_less);
+}
+
+
+// Creation of a level from an int - should return the default debug level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromIntOneArg) {
+
+    // Check that a valid debug level is as expected
+    LevelPtr debug42 = XDebugLevel::toLevel(
+        XDebugLevel::XDEBUG_MIN_LEVEL_INT - 42);
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(42) == *debug42);
+
+    // ... and that an invalid one returns an object of type debug.
+    LevelPtr debug_invalid = XDebugLevel::toLevel(Level::getInfo()->toInt());
+    EXPECT_TRUE(*Level::getDebug() == *debug_invalid);
+}
+
+
+// Creation of a level from an int - should return the default level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromIntTwoArg) {
+
+    // Check that a valid debug level is as expected
+    LevelPtr debug42 = XDebugLevel::toLevel(
+        (XDebugLevel::XDEBUG_MIN_LEVEL_INT - 42), Level::getFatal());
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(42) == *debug42);
+
+    // ... and that an invalid one returns an object of type debug.
+    LevelPtr debug_invalid = XDebugLevel::toLevel(
+        Level::getInfo()->toInt(), Level::getFatal());
+    EXPECT_TRUE(*Level::getFatal() == *debug_invalid);
+}
+
+
+// Creation of a level from a string - should return the default debug level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromStringOneArg) {
+
+    // Check that a valid debug levels are as expected
+    LevelPtr debug85 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG85"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(85) == *debug85);
+
+    LevelPtr debug92 = XDebugLevel::toLevelLS(LOG4CXX_STR("debug92"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(92) == *debug92);
+
+    LevelPtr debug27 = XDebugLevel::toLevelLS(LOG4CXX_STR("Debug27"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(27) == *debug27);
+
+    LevelPtr debug0 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug0);
+
+    // ... and that an invalid one returns an object of type debug (which is
+    // the equivalent of a debug level 0 object).
+    LevelPtr debug_invalid1 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBU"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid1);
+
+    LevelPtr debug_invalid2 = XDebugLevel::toLevelLS(LOG4CXX_STR("EBU"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid2);
+
+    LevelPtr debug_invalid3 = XDebugLevel::toLevelLS(LOG4CXX_STR(""));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid3);
+
+    LevelPtr debug_invalid4 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUGTEN"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug_invalid4);
+
+    LevelPtr debug_invalid5 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG105"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL) ==
+        *debug_invalid5);
+
+    LevelPtr debug_invalid6 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG-7"));
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL) ==
+        *debug_invalid6);
+}
+
+
+// Creation of a level from a string - should return the default level
+// if outside the range.
+
+TEST_F(XDebugLevelTest, FromStringTwoArg) {
+
+    // Check that a valid debug levels are as expected
+    LevelPtr debug85 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG85"),
+            Level::getFatal());
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(85) == *debug85);
+
+    LevelPtr debug92 = XDebugLevel::toLevelLS(LOG4CXX_STR("debug92"),
+            Level::getFatal());
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(92) == *debug92);
+
+    LevelPtr debug27 = XDebugLevel::toLevelLS(LOG4CXX_STR("Debug27"),
+            Level::getFatal());
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(27) == *debug27);
+
+    LevelPtr debug0 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG"),
+            Level::getFatal());
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(0) == *debug0);
+
+    // ... and that an invalid one returns an object of type debug (which is
+    // the equivalent of a debug level 0 object).
+    LevelPtr debug_invalid1 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBU"),
+            Level::getFatal());
+    EXPECT_TRUE(*Level::getFatal() == *debug_invalid1);
+
+    LevelPtr debug_invalid2 = XDebugLevel::toLevelLS(LOG4CXX_STR("EBU"),
+            Level::getFatal());
+    EXPECT_TRUE(*Level::getFatal() == *debug_invalid2);
+
+    LevelPtr debug_invalid3 = XDebugLevel::toLevelLS(LOG4CXX_STR(""),
+            Level::getFatal());
+    EXPECT_TRUE(*Level::getFatal() == *debug_invalid3);
+
+    LevelPtr debug_invalid4 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUGTEN"),
+            Level::getFatal());
+    EXPECT_TRUE(*Level::getFatal() == *debug_invalid4);
+
+    LevelPtr debug_invalid5 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG105"),
+            Level::getFatal());
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MAX_DEBUG_LEVEL) ==
+        *debug_invalid5);
+
+    LevelPtr debug_invalid6 = XDebugLevel::toLevelLS(LOG4CXX_STR("DEBUG-7"),
+            Level::getFatal());
+    EXPECT_TRUE(*XDebugLevel::getExtendedDebug(MIN_DEBUG_LEVEL) ==
+        *debug_invalid6);
+}

+ 145 - 0
src/lib/log/xdebuglevel.cc

@@ -0,0 +1,145 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+ 
+#include <cassert>
+#include <algorithm>
+#include <syslog.h>
+#include <string.h>
+#include <boost/lexical_cast.hpp>
+
+#include <xdebuglevel.h>
+#include <dbglevels.h>
+#include <log4cxx/helpers/stringhelper.h>
+
+using namespace log4cxx;
+using namespace log4cxx::helpers;
+
+// Storage for the logging level objects corresponding to each debug level
+
+bool XDebugLevel::dbglevels_unset_ = true;
+LevelPtr XDebugLevel::dbglevels_[NUM_DEBUG_LEVEL];
+
+// Register the class
+
+IMPLEMENT_LOG4CXX_LEVEL(XDebugLevel)
+
+
+// Create Extended Debug Level Objects
+
+LevelPtr XDebugLevel::getExtendedDebug(int level) {
+
+    // Initialize the logging levels corresponding to the possible range of
+    // debug if we have not already done so
+    if (dbglevels_unset_) {
+
+        // Asserting that the minimum debug level is zero - so corresponds
+        // to DEBUG_INT - means that the lowest level is set to main DEBUG
+        // level.  This means that the existing logging level object can be
+        // used.
+        assert(MIN_DEBUG_LEVEL == 0);
+        dbglevels_[0] = Level::getDebug();
+
+        // Create the logging level objects for the rest of the debug levels.
+        // They are given names of the form DEBUG<debug level> (e.g. DEBUG42).
+        // They will all correspond to a syslog level of DEBUG.
+        for (int i = 1; i < NUM_DEBUG_LEVEL; ++i) {
+            std::string name = std::string("DEBUG") +
+                boost::lexical_cast<std::string>(i);
+            dbglevels_[i] = new XDebugLevel(
+                (XDebugLevel::XDEBUG_MIN_LEVEL_INT - i),
+                LOG4CXX_STR(name.c_str()), LOG_DEBUG);
+        }
+        dbglevels_unset_ = false;
+    }
+
+    // Now get the logging level object asked for.  Coerce the debug level to
+    // lie in the acceptable range.
+    int actual = std::max(MIN_DEBUG_LEVEL, std::min(MAX_DEBUG_LEVEL, level));
+
+    // ... and return a pointer to the appropriate logging level object
+    return dbglevels_[actual - MIN_DEBUG_LEVEL];
+}
+
+// Convert an integer (an absolute logging level number, not a debug level) to a
+// logging level object.  If it lies outside the valid range, an object
+// corresponding to the minimum debug value is returned.
+
+LevelPtr XDebugLevel::toLevel(int val) {
+    return toLevel(val, getExtendedDebug(MIN_DEBUG_LEVEL));
+}
+
+LevelPtr XDebugLevel::toLevel(int val, const LevelPtr& defaultLevel) {
+
+    // Note the reversal of the notion of MIN and MAX - see the header file for
+    // details.
+    if ((val >= XDEBUG_MAX_LEVEL_INT) && (val <= XDEBUG_MIN_LEVEL_INT)) {
+        return getExtendedDebug(XDEBUG_MIN_LEVEL_INT - val);
+    }
+    else {
+        return defaultLevel;
+    }
+}
+
+// Convert string passed to a logging level or return default level.
+
+LevelPtr XDebugLevel::toLevelLS(const LogString& sArg) {
+    return toLevelLS(sArg, getExtendedDebug(0));
+}
+
+LevelPtr XDebugLevel::toLevelLS(const LogString& sArg,
+    const LevelPtr& defaultLevel)
+{
+    std::string name = sArg;        // Get to known type
+    size_t length = name.size();    // Length of the string
+
+    if (length < 5) {
+
+        // String can't possibly start DEBUG so we don't know what it is.
+        return defaultLevel;
+    }
+    else {
+        if (strncasecmp(name.c_str(), "DEBUG", 5) == 0) {
+
+            // String starts "DEBUG" (or "debug" or any case mixture).  The
+            // rest of the string -if any - should be a number.
+            if (length == 5) {
+
+                // It is plain "DEBUG".  Take this as level 0.
+                return getExtendedDebug(0);
+            }
+            else {
+
+                // Try converting the remainder to an integer.  The "5" is
+                // the length of the string "DEBUG".  Note that if the number
+                // is outside the rangeof debug levels, it is coerced to the
+                // nearest limit.  Thus a level of DEBUG509 will end up as
+                // if DEBUG99 has been specified.
+                try {
+                    int level = boost::lexical_cast<int>(name.substr(5));
+                    return getExtendedDebug(level);
+                }
+                catch (boost::bad_lexical_cast&) {
+                    return defaultLevel;
+                }
+            }
+        }
+        else {
+
+            // Unknown string - return default
+            return defaultLevel;
+        }
+    }
+}

+ 164 - 0
src/lib/log/xdebuglevel.h

@@ -0,0 +1,164 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __XDEBUGLEVEL_H
+#define __XDEBUGLEVEL_H
+
+#include <syslog.h>
+#include <log4cxx/level.h>
+
+#include <dbglevels.h>
+
+namespace log4cxx {
+
+/// \brief Debug Extension to Level Class
+///
+/// Based on the example given in the log4cxx distribution, this extends the
+/// log4cxx Level class to allow 100 debug levels.
+///
+/// First some terminology, as the use of the term "level" gets confusing.  The
+/// code and comments here use the term "level" in two contexts:
+///
+/// Logging level: The category of messages to log.  By default log4cxx defines
+/// the following logging levels: OFF, FATAL, ERROR, WARNING, INFO, DEBUG,
+/// TRACE, ALL.  Within the context of BIND-10, OFF, TRACE and ALL are not used
+/// and the idea of DEBUG has been extended, as will be seen below.
+///
+/// Debug level: This is a number that ranges from 0 to 99 and is used by the
+/// application to control the detail of debug output.  A value of 0 gives the
+/// highest-level debug output; a value of 99 gives the most verbose and most
+/// detailed. Debug messages (or whatever debug level) are only ever output
+/// when the logging level is set to DEBUG.
+///
+///
+/// With log4cxx, the various logging levels have a numeric value associated
+/// with them, such that FATAL > ERROR > WARNING etc.  This suggests that the
+/// idea of debug levels can be incorporated into the existing logging level
+/// scheme by assigning them appropriate numeric values, i.e.
+///
+/// WARNING > INFO > DEBUG(0) > DEBUG(2) > ... > DEBUG(99)
+///
+/// Setting a numeric level of DEBUG enables the basic messages; setting lower
+/// numeric levels will enable progressively more messages.  The lowest debug
+/// level (0) is chosen such that setting the general DEBUG logging level will
+/// automatically select that debug level.
+///
+/// This sub-class is needed because the log4cxx::Level class does not allow
+/// the setting of the numeric value of the current level to something other
+/// than the values enumerated in the class.  It creates a set of log4cxx
+/// logging levels to correspond to the various debug levels.  These levels have
+/// names in the range DEBUG1 to DEBUG99 (the existing Level DEBUG is used for
+/// a debug level of 0), although they are not used in BIND-10: instead the
+/// BIND-10 Logger class treats the logging levels and debug levels separately
+/// and combines them to choose the underlying log4cxx logging level.
+
+
+/// \brief Debug-Extended Level
+
+class XDebugLevel : public Level {
+    DECLARE_LOG4CXX_LEVEL(XDebugLevel)
+
+    /// Array of pointers to logging level objects, one for each debug level.
+    /// The pointer corresponding to a debug level of 0 points to the DEBUG
+    /// logging level object.
+    static LevelPtr dbglevels_[NUM_DEBUG_LEVEL];
+    static bool     dbglevels_unset_;
+
+public:
+
+    // Minimum and maximum debug levels.  Note that XDEBUG_MIN_LEVEL_INT is the
+    // number corresponding to the minimum debug level - and is actually larger
+    // that XDEBUG_MAX_LEVEL_INT, the number corresponding to the maximum debug
+    // level.
+    enum {
+        XDEBUG_MIN_LEVEL_INT = Level::DEBUG_INT - MIN_DEBUG_LEVEL,
+        XDEBUG_MAX_LEVEL_INT = Level::DEBUG_INT - MAX_DEBUG_LEVEL
+    };
+
+    /// \brief Constructor
+    ///
+    /// \param level Numeric value of the logging level.
+    /// \param name Name given to this logging level.
+    /// \param syslogEquivalent The category to be used by syslog when it logs
+    /// an event associated with the specified logging level.
+    XDebugLevel(int level, const LogString& name, int syslogEquivalent) :
+        Level(level, name, syslogEquivalent)
+    {}
+
+    /// \brief Create Logging Level Object
+    ///
+    /// Creates a logging level object corresponding to one of the debug levels.
+    ///
+    /// \param dbglevel The debug level, which ranges from MIN_DEBUG_LEVEL to
+    /// MAX_DEBUG_LEVEL. It is coerced to that range if it lies outside it.
+    ///
+    /// \return Pointer to the desired logging level object.
+    static LevelPtr getExtendedDebug(int dbglevel);
+
+    /// \brief Convert Integer to a Logging Level
+    ///
+    /// Returns a logging level object corresponding to the given value (which
+    /// is an absolute value of a logging level - it is not a debug level).
+    /// If the number is invalid, an object of logging level DEBUG (the
+    /// minimum debug logging level) is returned.
+    ///
+    /// \param val Number to convert to a logging level.  This is an absolute
+    /// logging level number, not a debug level.
+    ///
+    /// \return Pointer to the desired logging level object.
+    static LevelPtr toLevel(int val);
+
+    /// \brief Convert Integer to a Level
+    ///
+    /// Returns a logging level object corresponding to the given value (which
+    /// is an absolute value of a logging level - it is not a debug level).
+    /// If the number is invalid, the given default is returned.
+    ///
+    /// \param val Number to convert to a logging level.  This is an absolute
+    /// logging level number, not a debug level.
+    /// \param defaultLevel Logging level to return if value is not recognised.
+    ///
+    /// \return Pointer to the desired logging level object.
+    static LevelPtr toLevel(int val, const LevelPtr& defaultLevel);
+
+    /// \param Convert String to Logging Level
+    ///
+    /// Returns a logging level object corresponding to the given name.  If the
+    /// name is invalid, an object of logging level DEBUG (the minimum debug
+    /// logging level) is returned.
+    ///
+    /// \param sArg Name of the logging level.
+    ///
+    /// \return Pointer to the desired logging level object.
+    static LevelPtr toLevelLS(const LogString& sArg);
+
+    /// \param Convert String to Logging Level
+    ///
+    /// Returns a logging level object corresponding to the given name.  If the
+    /// name is invalid, the given default is returned.
+    ///
+    /// \param sArg name of the level.
+    /// \param defaultLevel Logging level to return if name does not exist.
+    ///
+    /// \return Pointer to the desired logging level object.
+    static LevelPtr toLevelLS(const LogString& sArg,
+        const LevelPtr& defaultLevel);
+};
+
+} // namespace log4cxx
+
+
+#endif // __XDEBUGLEVEL_H