Browse Source

[master] Merge branch 'trac438'

Conflicts:
	configure.ac
Stephen Morris 14 years ago
parent
commit
7b1606cea7
45 changed files with 5595 additions and 3 deletions
  1. 50 0
      configure.ac
  2. 1 1
      src/lib/asiolink/tests/Makefile.am
  3. 37 2
      src/lib/log/Makefile.am
  4. 20 0
      src/lib/log/compiler/Makefile.am
  5. 450 0
      src/lib/log/compiler/message.cc
  6. 31 0
      src/lib/log/dbglevels.h
  7. 371 0
      src/lib/log/documentation.txt
  8. 140 0
      src/lib/log/filename.cc
  9. 163 0
      src/lib/log/filename.h
  10. 307 0
      src/lib/log/logger.cc
  11. 327 0
      src/lib/log/logger.h
  12. 116 0
      src/lib/log/logger_support.cc
  13. 43 0
      src/lib/log/logger_support.h
  14. 116 0
      src/lib/log/message_dictionary.cc
  15. 150 0
      src/lib/log/message_dictionary.h
  16. 28 0
      src/lib/log/message_exception.cc
  17. 90 0
      src/lib/log/message_exception.h
  18. 32 0
      src/lib/log/message_initializer.cc
  19. 63 0
      src/lib/log/message_initializer.h
  20. 184 0
      src/lib/log/message_reader.cc
  21. 175 0
      src/lib/log/message_reader.h
  22. 32 0
      src/lib/log/message_types.h
  23. 27 0
      src/lib/log/messagedef.cc
  24. 24 0
      src/lib/log/messagedef.h
  25. 82 0
      src/lib/log/messagedef.mes
  26. 26 0
      src/lib/log/root_logger_name.cc
  27. 66 0
      src/lib/log/root_logger_name.h
  28. 138 0
      src/lib/log/strutil.cc
  29. 147 0
      src/lib/log/strutil.h
  30. 45 0
      src/lib/log/tests/Makefile.am
  31. 181 0
      src/lib/log/tests/filename_unittest.cc
  32. 23 0
      src/lib/log/tests/localdef.mes
  33. 109 0
      src/lib/log/tests/logger_support_test.cc
  34. 395 0
      src/lib/log/tests/logger_unittest.cc
  35. 173 0
      src/lib/log/tests/message_dictionary_unittest.cc
  36. 72 0
      src/lib/log/tests/message_initializer_unittest.cc
  37. 41 0
      src/lib/log/tests/message_initializer_unittest_2.cc
  38. 228 0
      src/lib/log/tests/message_reader_unittest.cc
  39. 52 0
      src/lib/log/tests/root_logger_name_unittest.cc
  40. 84 0
      src/lib/log/tests/run_time_init_test.sh.in
  41. 23 0
      src/lib/log/tests/run_unittests.cc
  42. 216 0
      src/lib/log/tests/strutil_unittest.cc
  43. 205 0
      src/lib/log/tests/xdebuglevel_unittest.cc
  44. 148 0
      src/lib/log/xdebuglevel.cc
  45. 164 0
      src/lib/log/xdebuglevel.h

+ 50 - 0
configure.ac

@@ -363,6 +363,50 @@ if test "$lcov" != "no"; then
 fi
 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";
+   log4cxx_library_path="${withval}/lib"
+  ])
+
+# If not specified, try some common paths.  These default to
+# /usr/include and /usr/lib 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
+			log4cxx_library_path=$d/lib
+			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)
+
+LOG4CXX_LDFLAGS="-llog4cxx";
+if test "${log4cxx_library_path}"; then
+    LOG4CXX_LDFLAGS="-L${log4cxx_library_path} -llog4cxx"
+fi
+AC_SUBST(LOG4CXX_LDFLAGS)
+
+
+
 #
 # Configure Boost header path
 #
@@ -652,6 +696,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/datasrc/tests/Makefile
                  src/lib/xfr/Makefile
                  src/lib/log/Makefile
+                 src/lib/log/compiler/Makefile
+                 src/lib/log/tests/Makefile
                  src/lib/resolve/Makefile
                  src/lib/resolve/tests/Makefile
                  src/lib/testutils/Makefile
@@ -713,6 +759,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/dns/tests/testdata/gen-wiredata.py
            src/lib/cc/session_config.h.pre
            src/lib/cc/tests/session_unittests_config.h
+           src/lib/log/tests/run_time_init_test.sh
           ], [
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
@@ -736,6 +783,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/bin/msgq/tests/msgq_test
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
+           chmod +x src/lib/log/tests/run_time_init_test.sh
           ])
 AC_OUTPUT
 
@@ -763,6 +811,8 @@ dnl includes too
                  ${PYTHON_LDFLAGS}
                  ${PYTHON_LIB}
   Boost:         ${BOOST_INCLUDES}
+  log4cxx:       ${LOG4CXX_INCLUDES}
+                 ${LOG4CXX_LDFLAGS}
   SQLite:        $SQLITE_CFLAGS
                  $SQLITE_LIBS
 

+ 1 - 1
src/lib/asiolink/tests/Makefile.am

@@ -20,7 +20,7 @@ run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += asiolink_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(SQLITE_LIBS)
 run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/libdns++.la

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

@@ -1,4 +1,39 @@
-AM_CXXFLAGS = $(B10_CXXFLAGS)
+SUBDIRS = . compiler tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += $(LOG4CXX_INCLUDES)
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+
+CLEANFILES = *.gcno *.gcda
 
 lib_LTLIBRARIES = liblog.la
-liblog_la_SOURCES = dummylog.cc dummylog.h
+liblog_la_SOURCES  =
+liblog_la_SOURCES += dbglevels.h
+liblog_la_SOURCES += dummylog.h dummylog.cc Message.h
+liblog_la_SOURCES += filename.h filename.cc
+liblog_la_SOURCES += logger.cc logger.h
+liblog_la_SOURCES += logger_support.cc logger_support.h
+liblog_la_SOURCES += messagedef.cc messagedef.h
+liblog_la_SOURCES += message_dictionary.cc message_dictionary.h
+liblog_la_SOURCES += message_exception.h message_exception.cc
+liblog_la_SOURCES += message_initializer.cc message_initializer.h
+liblog_la_SOURCES += message_reader.cc message_reader.h
+liblog_la_SOURCES += message_types.h
+liblog_la_SOURCES += root_logger_name.cc root_logger_name.h
+liblog_la_SOURCES += strutil.h strutil.cc
+liblog_la_SOURCES += xdebuglevel.cc xdebuglevel.h
+
+liblog_la_LDFLAGS = $(LOG4CXX_LDFLAGS)
+
+# 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)

+ 20 - 0
src/lib/log/compiler/Makefile.am

@@ -0,0 +1,20 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda
+
+pkglibexec_PROGRAMS = message
+message_SOURCES = message.cc
+message_LDADD  = $(top_builddir)/src/lib/log/liblog.la
+

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

@@ -0,0 +1,450 @@
+// 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 <cctype>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <errno.h>
+#include <getopt.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <log/filename.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+#include <log/messagedef.h>
+#include <log/strutil.h>
+
+#include <log/logger.h>
+
+using namespace std;
+using namespace isc::log;
+
+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 Create Time
+///
+/// Returns the current time as a suitably-formatted string.
+///
+/// \return Current time
+
+static string currentTime() {
+
+    // Get the current time.
+    time_t curtime;
+    time(&curtime);
+
+    // Format it
+    char buffer[32];
+    ctime_r(&curtime, buffer);
+
+    // Convert to string and strip out the trailing newline
+    string current_time = buffer;
+    return isc::strutil::trim(current_time);
+}
+
+
+
+
+/// \brief Create Header Sentinel
+///
+/// Given the name of a file, create an #ifdef sentinel name.  The name is
+/// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
+/// extension less the leading period.  The sentinel will be upper-case.
+///
+/// \param file Filename object representing the file.
+///
+/// \return Sentinel name
+
+static string sentinel(Filename& file) {
+
+    string name = file.name();
+    string ext = file.extension();
+    string sentinel_text = "__" + name + "_" + ext.substr(1);
+    isc::strutil::uppercase(sentinel_text);
+    return sentinel_text;
+}
+
+
+/// \brief Quote String
+///
+/// Inserts an escape character (a backslash) prior to any double quote
+/// characters.  This is used to handle the fact that the input file does not
+/// contain quotes, yet the string will be included in a C++ literal string.
+
+string quoteString(const string& instring) {
+
+    // Create the output string and reserve the space needed to hold the input
+    // string. (Most input strings will not contain quotes, so this single
+    // reservation should be all that is needed.) 
+    string outstring;
+    outstring.reserve(instring.size());
+
+    // Iterate through the input string, preceding quotes with a slash.
+    for (size_t i = 0; i < instring.size(); ++i) {
+        if (instring[i] == '"') {
+            outstring += '\\';
+        }
+        outstring += instring[i];
+    }
+
+    return outstring;
+}
+
+
+/// \brief Sorted Identifiers
+///
+/// Given a dictionary, return a vector holding the message IDs in sorted
+/// order.
+///
+/// \param dictionary Dictionary to examine
+///
+/// \return Sorted list of message IDs
+
+vector<MessageID> sortedIdentifiers(MessageDictionary* dictionary) {
+    vector<MessageID> ident;
+
+    for (MessageDictionary::const_iterator i = dictionary->begin();
+         i != dictionary->end(); ++i) {
+        ident.push_back(i->first);
+    }
+    sort(ident.begin(), ident.end());
+
+    return ident;
+}
+
+
+/// \brief Write Header File
+///
+/// Writes the C++ header file containing the symbol definitions.
+///
+/// \param file Name of the message file.  The header file is written to a
+/// file of the same name but with a .h suffix.
+/// \param prefix Prefix string to use in symbols
+/// \param dictionary Dictionary holding the message definitions.
+
+void writeHeaderFile(const string& file, const string& prefix,
+    MessageDictionary* dictionary)
+{
+    Filename message_file(file);
+    Filename header_file(message_file.useAsDefault(".h"));
+
+    // Text to use as the sentinels.
+    string sentinel_text = sentinel(header_file);
+
+    // Open the output file for writing
+    ofstream hfile(header_file.fullName().c_str());
+
+    try {
+        if (hfile.fail()) {
+            throw MessageException(MSG_OPENOUT, header_file.fullName(),
+                strerror(errno));
+        }
+
+        // Write the header preamble.  If there is an error, we'll pick it up
+        // after the last write.
+
+        hfile <<
+            "// File created from " << message_file.fullName() << " on " <<
+                currentTime() << "\n" <<
+             "\n" <<
+             "#ifndef " << sentinel_text << "\n" <<
+             "#define "  << sentinel_text << "\n" <<
+             "\n" <<
+             "#include <log/message_types.h>\n" <<
+             "\n" <<
+             "namespace {\n" <<
+             "\n";
+
+        vector<MessageID> idents = sortedIdentifiers(dictionary);
+        for (vector<MessageID>::const_iterator j = idents.begin();
+            j != idents.end(); ++j) {
+            hfile << "isc::log::MessageID " << prefix << *j <<
+                " = \"" << *j << "\";\n";
+        }
+
+        // ... and finally the postamble
+        hfile <<
+            "\n" <<
+            "} // Anonymous namespace\n" <<
+            "\n" <<
+            "#endif // " << sentinel_text << "\n";
+
+        // Report errors (if any) and exit
+        if (hfile.fail()) {
+            throw MessageException(MSG_WRITERR, header_file.fullName(),
+                strerror(errno));
+        }
+
+        hfile.close();
+    }
+    catch (MessageException&) {
+        hfile.close();
+        throw;
+    }
+}
+
+
+/// \brief Convert Non Alpha-Numeric Characters to Underscores
+///
+/// Simple function for use in a call to transform
+
+char replaceNonAlphaNum(char c) {
+    return (isalnum(c) ? c : '_');
+}
+
+
+/// \brief Write Program File
+///
+/// Writes the C++ source code file.  This defines an external objects whose
+/// constructor is run at initialization time.  The constructor adds the message
+/// definitions to the main global dictionary.
+
+void writeProgramFile(const string& file, MessageDictionary* dictionary)
+{
+    Filename message_file(file);
+    Filename program_file(message_file.useAsDefault(".cc"));
+
+    // Open the output file for writing
+    ofstream ccfile(program_file.fullName().c_str());
+    try {
+        if (ccfile.fail()) {
+            throw MessageException(MSG_OPENOUT, program_file.fullName(),
+                strerror(errno));
+        }
+
+        // Write the preamble.  If there is an error, we'll pick it up after
+        // the last write.
+
+        ccfile <<
+            "// File created from " << message_file.fullName() << " on " <<
+                currentTime() << "\n" <<
+             "\n" <<
+             "#include <cstddef>\n" <<
+             "#include <log/message_initializer.h>\n" <<
+             "\n" <<
+             "using namespace isc::log;\n" <<
+             "\n" <<
+             "namespace {\n" <<
+             "\n" <<
+             "const char* values[] = {\n";
+
+        // Output the identifiers and the associated text.
+        vector<MessageID> idents = sortedIdentifiers(dictionary);
+        for (vector<MessageID>::const_iterator i = idents.begin();
+            i != idents.end(); ++i) {
+                ccfile << "    \"" << *i << "\", \"" <<
+                    quoteString(dictionary->getText(*i)) << "\",\n";
+        }
+
+        // ... and the postamble
+        ccfile <<
+            "    NULL\n" <<
+            "};\n" <<
+            "\n" <<
+            "} // Anonymous namespace\n" <<
+            "\n";
+
+        // Now construct a unique name.  We don't put the message initializer as
+        // a static variable or in an anonymous namespace lest the C++
+        // compiler's optimizer decides it can optimise it away.
+        string unique_name = program_file.name() + program_file.extension() +
+            "_" + currentTime();
+        transform(unique_name.begin(), unique_name.end(), unique_name.begin(),
+            replaceNonAlphaNum);
+
+        // ... and write the initialization code
+        ccfile <<
+            "MessageInitializer " << unique_name << "(values);\n";
+
+        // Report errors (if any) and exit
+        if (ccfile.fail()) {
+            throw MessageException(MSG_WRITERR, program_file.fullName(),
+                strerror(errno));
+        }
+
+        ccfile.close();
+    }
+    catch (MessageException&) {
+        ccfile.close();
+        throw;
+    }
+}
+
+
+/// \brief Warn of Duplicate Entries
+///
+/// If the input file contained duplicate message IDs, only the first will be
+/// processed.  However, we should warn about it.
+///
+/// \param reader Message Reader used to read the file
+
+static void warnDuplicates(MessageReader& reader) {
+
+    // Get the duplicates (the overflow) and, if present, sort them into some
+    // order and remove those which occur more than once (which mean that they
+    // occur more than twice in the input file).
+    MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
+    if (duplicates.size() > 0) {
+        cout << "Warning: the following duplicate IDs were found:\n";
+
+        sort(duplicates.begin(), duplicates.end());
+        MessageReader::MessageIDCollection::iterator new_end =
+            unique(duplicates.begin(), duplicates.end());
+        for (MessageReader::MessageIDCollection::iterator i = duplicates.begin();
+            i != new_end; ++i) {
+            cout << "    " << *i << "\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'},
+        {"version", no_argument, NULL, 'v'},
+        {NULL,      0,           NULL, 0  }
+    };
+    const char* soptions = "hv";               // Short options
+
+    optind = 1;             // Ensure we start a new scan
+    int  opt;               // Value of the option
+
+    while ((opt = getopt_long(argc, argv, soptions, loptions, NULL)) != -1) {
+        switch (opt) {
+            case 'h':
+                usage();
+                return 0;
+
+            case 'v':
+                version();
+                return 0;
+
+            default:
+                // A message will have already been output about the error.
+                return 1;
+        }
+    }
+
+    // Do we have the message file?
+    if (optind < (argc - 1)) {
+        cout << "Error: excess arguments in command line\n";
+        usage();
+        return 1;
+    } else if (optind >= argc) {
+        cout << "Error: missing message file\n";
+        usage();
+        return 1;
+    }
+    string message_file = argv[optind];
+
+    try {
+        // Have identified the file, so process it.  First create a local
+        // dictionary into which the data will be put.
+        MessageDictionary dictionary;
+
+        // Read the data into it.
+        MessageReader reader(&dictionary);
+        reader.readFile(message_file);
+
+        // Now write the header file.
+        writeHeaderFile(message_file, reader.getPrefix(), &dictionary);
+
+        // ... and the message text file.
+        writeProgramFile(message_file, &dictionary);
+
+        // Finally, warn of any duplicates encountered.
+        warnDuplicates(reader);
+    }
+    catch (MessageException& e) {
+        // Create an error message from the ID and the text
+        MessageDictionary* global = MessageDictionary::globalDictionary();
+        string text = e.id() + ", " + global->getText(e.id());
+
+        // Format with arguments
+        text = isc::strutil::format(text, e.arguments());
+        cerr << text << "\n";
+
+        return 1;
+    }
+
+    return 0;
+
+}

+ 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

+ 371 - 0
src/lib/log/documentation.txt

@@ -0,0 +1,371 @@
+This directory holds the first release of the logging system.
+
+Basic Ideas
+===========
+The BIND-10 logging system merges two ideas:
+
+* A hierarchical logging system similar to that used in Java (i.e. log4j)
+* Separation of message definitions and text
+
+
+Hierarchical Logging System
+===========================
+When a program writes a message to the logging system, it does so using an
+instance of the Logger class.  As well as performing the write of the message,
+the logger identifies the source of the message: different sources can write
+to different destinations and can log different severities of messages.  For
+example, the "cache" logger could write messages of DEBUG severity or above
+to a file while all other components write messages of "INFO" severity or above
+to the Syslog file.
+
+The loggers are hierarchical in that each logger is the child of another logger.
+The top of the hierarchy is the root logger, which does not have a parent.  The
+point of the hierarchy is that unless a logger is explicitly assigned an
+attribute (such as severity of message being logger), it picks it up from the
+parent.  (In BIND-10, there is the root logger (named after the program) and
+every other logger is a child of that.)  So in the example above, the
+INFO/Syslog attributes could be associated with the root logger while the
+DEBUG/file attributes are associated with the "cache" logger.
+
+
+Separation of Messages Definitions And Text
+===========================================
+The reason for this is to allow the message text to be overridden by versions
+in a local language.  To do this, each message is identified by an identifier
+e.g. "OPENIN".  Within the program, this is the symbol passed to the logging
+system.  The logger system uses the symbol as an index into a dictionary to
+retrieve the message associated with it (e.g. "unable to open %s for input").
+substitutes any message parameters (in this example, the string that is an
+invalid filename) and logs it to the destination.
+
+In the BIND-10 system, a set of default messages are linked into the program.
+At run-time. each program reads a message file, updating the stored definitions;
+this updated text is logged.  However, to aid support, the message identifier
+so in the example above, the message finally logged would be something like:
+
+    OPENIN, unable to open a.txt for input
+
+
+Using The System
+================
+The steps in using the system are:
+
+1. Create a message file.  This defines messages by an identification - a
+   mnemonic for the message, typically 6-12 characters long - and a message.
+   The file is described in more detail below.
+
+   Ideally the file should have a file type of ".msg".
+
+2. Run it through the message compiler to produce the .h and .cc files.  It
+   is intended that this step be included in the build process.  However, for
+   not run the compiler (found in the "compiler" subdirectory) manually.  The
+   only argument is the name of the message file: it will produce as output
+   two files, having the same name as the input file but with file types of
+   ".h" and ".cc".
+
+   The compiler is built in the "compiler" subdirectory of the "src/lib/log"
+   directory.
+
+3. Include the .h file in your source code to define message symbols, and
+   make sure that the .cc file is compiled and linked into your program -
+   static initialization will add the symbols to the global dictionary.
+
+4. Declare loggers in your code and use them to log messages.  This is described
+   in more detail below.
+
+5. To set the debug level and run-time message file, call runTimeInit (declared
+   in logger_support.h) in the main program unit.  This is a temporary solution
+   for Year 2, and will be replaced at a later date, the information coming from
+   the configuration database.
+
+
+Message Files
+=============
+
+File Contents and Format
+------------------------
+A message file is a file containing message definitions.  Typically there will
+be one message file for each component that declares message symbols.  An
+example file could be:
+
+-- BEGIN --
+
+# 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.
+
+-- END --
+
+Points to note:
+* Leading and trailing space are trimmed from the line.  Although the above
+  exampl,e has every line starting at column 1, the lines could be indented if
+  desired.
+
+* Blank lines are ignored.
+
+* Lines starting with "#" are comments are are ignored.  Comments must be on
+  a line by themselves - inline comments will be interpreted as part of the
+  text of the line.
+
+* 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. (Prefixes
+  are explained below.)
+
+* Lines starting + indicate an explanation for the preceding message.  These
+  are intended to be processed by a separate program and used to generate an
+  error messages manual.  However they are treated like comments by the message
+  compiler.  As with comments, these must be on a line by themselves; if inline,
+  the text (including the leading "+") will be interpreted as part of the line.
+
+* Message lines.  These comprise a symbol name and a message, which may
+  include zero or more printf-style tokens.  Symbol names will be upper-cased
+  by the compiler.
+
+
+Message Compiler
+----------------
+The message compiler is a program built in the src/log/compiler directory.
+It processes the message file to produce two files:
+
+1) A C++ header file (called <message-file-name>.h) that holds lines of
+the form:
+
+   namespace {
+   isc::log::MessageID PREFIX_IDENTIFIER = "IDENTIFIER";
+      :
+   }
+
+The symbols define the keys in the global message dictionary.  At present
+they are defined as std::strings, but a future implementation could redefine
+them as numeric values.
+
+The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
+the argument to the directive.  So "$PREFIX MSG_" would prefix the identifer
+ABC with "MSG_" to give the symbol MSG_ABC.  Similarly "$PREFIX E" would
+prefix it with "E" to give the symbol EABC.  If no $PREFIX is given, no
+prefix appears (so the symbol in this example would be ABC).
+
+
+2) A C++ source file (called <message-file-name>.cc) 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* values[] = {
+        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:
+
+    MessageInitializer <message-file-name>_<time>(values);
+
+(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 MessageInitializer object retrieves the singleton
+global Dictionary object (created using standard methods to avoid the
+"static initialization fiasco") and adds each identifier and text to it.
+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).
+
+
+Using the Logging
+=================
+To use the current version of the logging:
+
+1. Build message header file and source file as describe above.
+
+2. In the main module of the program, declare an instance of the
+   RootLoggerName class to define the name of the program's root logger, e.g.
+
+       #include <log/root_logger_name.h>
+
+       isc::log::RootLoggerName("b10-auth");
+
+   It should be declared outside an execution unit to allow other statically-
+   declared loggers to pick it up.
+
+2. In the code that needs to do logging, declare a logger with a given name,
+   e.g.
+
+       #include <log/logger.h>
+            :
+       isc::log::Logger logger("myname");   // "myname" can be anything
+
+   The above example assumes declaration outside a function.  If declaring
+   non-statically within a function, declare it as:
+
+       isc::log::Logger logger("myname", true);
+
+   This is due to an apparent bug in the underlying log4cxx, where the deletion
+   of a statically-declared object at program termination can cause a segment
+   fault. (The destruction of internal memory structures can sometimes happen
+   out of order.)  By default the Logger class creates the structures in its
+   constructor but does not delete them in the destruction.  The default
+   behavious works because instead of reclaiming memory at program run-down,
+   the operating system reclaims it when the process is deleted.
+
+   Setting the second argument "true" causes the Logger's destructor to delete
+   the log4cxx structures.  This does not cause a problem if the program is
+   not terminating.  So use the second form when declaring an automatic
+   instance of isc::log::Logger on the stack.
+
+3. The main program unit should include a call to isc::log::runTimeInit()
+   (defined in logger_support.h) to set the logging severity, debug log level,
+   and external message file.
+
+   a) The logging severity is one of the enum defined in logger.h, i.e.
+
+        isc::log::Logger::DEBUG
+        isc::log::Logger::INFO
+        isc::log::Logger::WARN
+        isc::log::Logger::ERROR
+        isc::log::Logger::FATAL
+        isc::log::Logger::NONE
+
+   b) The debug log level is only interpreted when the severity is DEBUG and
+      is an integer raning from 0 to 99.  0 should be used for the highest-level
+      debug messages and 99 for the lowest-level (and typically more verbose)
+      messages.
+
+   c) Name of an external message file.  This is the same as a standard message
+      file, although it should not include the $PREFIX directive. (A single
+      $PREFIX directive will be ignored; multiple directives will cause the
+      read of the file to fail with an error.)  If a message is replaced, the
+      message should include the same printf-format directives in the same order
+      as the original message.
+
+4. Issue logging calls using methods on logger, e.g.
+
+       logger.error(DPS_NSTIMEOUT, "isc.org");
+
+   (where, in the example above we might have defined the symbol in the message
+   file with something along the lines of:
+
+       $PREFIX DPS_
+           :
+       NSTIMEOUT  queries to all nameservers for %s have timed out
+
+   At present, the only logging is to the console.
+
+
+Severity Guidelines
+===================
+When using logging, the question arises, what severity should a message be
+logged at?  The following is a suggestion - as always, the decision must be
+made in the context of which the message is logged.
+
+FATAL
+-----
+The program has encountered an error that is so severe that it cannot
+continue (or there is no point in continuing).  When a fatal error has been
+logged, the program will usually exit immediately (via a call to abort()) or
+shortly afterwards, after dumping some diagnostic information.
+
+ERROR
+-----
+Something has happened such that the program can continue but the results
+for the current (or future) operations cannot be guaranteed to be correct,
+or the results will be correct but the service is impaired.  For example,
+the program started but attempts to open one or more network interfaces failed.
+
+WARN
+----
+An unusual event  happened.  Although the program will continue working
+normally, the event was sufficiently out of the ordinary to warrant drawings
+attention to it.  For example, at program start-up a zone was loaded that
+contained no resource records,
+
+INFO
+----
+A normal but significant event has occurred that should be recorded,
+e.g. the program has started or is just about to terminate, a new zone has
+been created, etc.
+
+DEBUG
+-----
+This severity is only enabled on for debugging purposes.  A debug level is
+associated with debug messages, level 0 (the default) being for high-level
+messages and level 99 (the maximum) for the lowest level.  How the messages
+are distributed between the levels is up to the developer.  So if debugging
+the NSAS (for example), a level 0 message might record the creation of a new
+zone, a level 10 recording a timeout when trying to get a nameserver address,
+but a level 50 would record every query for an address. (And we might add
+level 51 to record every update of the RTT.)
+
+Note that like severities, levels are cumulative; so if level 25 is set as the
+debug level, all debug levels from 0 to 25 will be output.  In fact, it is
+probably easier to visualise the debug levels as part of the severity system:
+
+    FATAL                High
+    ERROR
+    WARN
+    INFO
+    DEBUG level 0
+    DEBUG level 1
+       :
+    DEBUG level 99       Low
+
+When a particular severity is set, it - and all severities and/or debug
+levels above it - will be logged.
+
+Logging Sources v Logging Severities
+------------------------------------
+When logging events, make a distinction between events related to the server
+and events related to DNS messages received.  Caution needs to be exercised
+with the latter as, if the logging is enabled in the normal course of events,
+such logging could be a denoial of service vector. For example, suppose that
+the main authoritiative service logger were to log both zone loading and
+unloading as INFO and a warning message if it received an invalid packet. An
+attacker could make the INFO messages unusable by flooding the server with
+malformed packets.
+
+There are two approaches to get round this:
+
+a) Make the logging of packet-dependent events a DEBUG-severity message.
+DEBUG is not enabled by default, so these events will not be recorded unless
+DEBUG is specifically chosen.
+
+b) Record system-related and packet-related messages via different loggers
+(e.g.  in the example given, sever events could be logged using the logger
+"auth" and packet-related events at that level logged using the logger
+"pkt-auth".)
+As the loggers are independent and the severity levels independent, fine-tuning
+of what and what is not recorded can be achieved.
+
+
+Outstanding Issues
+==================
+* Ability to configure system according to configuration database.
+* Update the build procedure to create .cc and .h files from the .msg file
+  during the build process. (Requires that the message compiler is built first.)
+
+
+Notes
+=====
+The message compiler is written in C++ (instead of Python) because it
+contains a component that reads the message file.  This component is used
+in both the message compiler and the server; in the server it is used when
+the server starts up (or when triggered by a command) to read in a message
+file to overwrite the internal dictionary.  Writing it in C++ means there
+is only one piece of code that does this functionality.
+

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

@@ -0,0 +1,140 @@
+// 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 <log/filename.h>
+#include <log/strutil.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 = isc::strutil::trim(defname);
+#ifdef WIN32
+    isc::strutil::normalizeSlash(copy_defname);
+#endif
+
+    // 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 = isc::strutil::trim(name);
+#ifdef WIN32
+    isc::strutil::normalizeSlash(copy_name);
+#endif
+
+    // 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 <strutil.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_ = isc::strutil::trim(name);
+#ifdef WIN32
+        isc::strutil::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

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

@@ -0,0 +1,307 @@
+// 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 <stdarg.h>
+#include <stdio.h>
+
+#include <log4cxx/appender.h>
+#include <log4cxx/basicconfigurator.h>
+#include <log4cxx/patternlayout.h>
+#include <log4cxx/consoleappender.h>
+
+#include <log/root_logger_name.h>
+#include <log/logger.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+#include <log/strutil.h>
+#include <log/xdebuglevel.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Static initializations
+
+bool Logger::init_ = false;
+
+// Destructor.  Delete log4cxx stuff if "don't delete" is clear.
+
+Logger::~Logger() {
+    if (exit_delete_) {
+        delete loggerptr_;
+    }
+}
+
+// Initialize logger - create a logger as a child of the root logger.  With
+// log4cxx this is assured by naming the logger <parent>.<child>.
+
+void
+Logger::initLogger() {
+
+    // Initialize basic logging if not already done.  This is a one-off for
+    // all loggers.
+    if (!init_) {
+
+        // TEMPORARY
+        // Add a suitable console logger to the log4cxx root logger.  (This
+        // is the logger at the root of the log4cxx tree, not the BIND-10 root
+        // logger, which is one level down.)  The chosen format is:
+        //
+        // YYYY-MM-DD hh:mm:ss.sss [logger] SEVERITY: text
+        //
+        // As noted, this is a temporary hack: it is done here to ensure that
+        // a suitable output and output pattern is set.  Future versions of the
+        // software will set this based on configuration data.
+
+        log4cxx::LayoutPtr layout(
+            new log4cxx::PatternLayout(
+                "%d{yyyy-MM-DD HH:mm:ss.SSS} %-5p [%c] %m\n"));
+        log4cxx::AppenderPtr console(
+            new log4cxx::ConsoleAppender(layout));
+        log4cxx::LoggerPtr sys_root_logger = log4cxx::Logger::getRootLogger();
+        sys_root_logger->addAppender(console);
+        
+        // Set the default logging to INFO
+        sys_root_logger->setLevel(log4cxx::Level::getInfo());
+
+        // All static stuff initialized
+        init_ = true;
+    }
+
+    // Initialize this logger.  Name this as to whether the BIND-10 root logger
+    // name has been set.  (If not, this mucks up the hierarchy :-( ).
+    string root_name = RootLoggerName::getName();
+    if (root_name.empty() || (name_ == root_name)) {
+        loggerptr_ = new log4cxx::LoggerPtr(log4cxx::Logger::getLogger(name_));
+    }
+    else {
+        loggerptr_ = new log4cxx::LoggerPtr(
+            log4cxx::Logger::getLogger(root_name + "." + name_)
+        );
+    }
+}
+
+
+// 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, WARN 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 WARN 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 WARN (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 = 9974.  As 9974 !>= 9975, it is not logged.  A
+// message of level 25 is, because 9975 >= 9975.
+//
+// The extended set of logging levels is implemented by the XDebugLevel class.
+
+void
+Logger::setSeverity(Severity severity, int dbglevel) {
+    switch (severity) {
+        case NONE:
+            getLogger()->setLevel(log4cxx::Level::getOff());
+            break;
+
+        case FATAL:
+            getLogger()->setLevel(log4cxx::Level::getFatal());
+            break;
+
+        case ERROR:
+            getLogger()->setLevel(log4cxx::Level::getError());
+            break;
+
+        case WARN:
+            getLogger()->setLevel(log4cxx::Level::getWarn());
+            break;
+
+        case INFO:
+            getLogger()->setLevel(log4cxx::Level::getInfo());
+            break;
+
+        case DEBUG:
+            getLogger()->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:
+            getLogger()->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 (WARN);
+    } 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() {
+
+    log4cxx::LevelPtr level = getLogger()->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);
+        }
+    }
+}
+
+// Log an error message:
+// Common code.  Owing to the use of variable arguments, this must be inline
+// (hence the definition of the macro).  Also note that it expects that the
+// message buffer "message" is declared in the compilation unit.
+
+#define MESSAGE_SIZE (256)
+
+#define FORMAT_MESSAGE(message) \
+    { \
+    MessageDictionary* global = MessageDictionary::globalDictionary(); \
+    string format = global->getText(ident); \
+    va_list ap; \
+    va_start(ap, ident); \
+    vsnprintf(message, sizeof(message), format.c_str(), ap); \
+    message[sizeof(message) - 1] = '\0'; \
+    va_end(ap); \
+    }
+    
+
+// Output methods
+
+void
+Logger::debug(int dbglevel, isc::log::MessageID ident, ...) {
+    if (isDebugEnabled(dbglevel)) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_DEBUG(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::info(isc::log::MessageID ident, ...) {
+    if (isInfoEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_INFO(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::warn(isc::log::MessageID ident, ...) {
+    if (isWarnEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_WARN(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::error(isc::log::MessageID ident, ...) {
+    if (isErrorEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_ERROR(getLogger(), ident << ", " << message);
+    }
+}
+
+void
+Logger::fatal(isc::log::MessageID ident, ...) {
+    if (isFatalEnabled()) {
+        char message[MESSAGE_SIZE];
+        FORMAT_MESSAGE(message);
+        LOG4CXX_FATAL(getLogger(), ident << ", " << message);
+    }
+}
+
+
+} // namespace log
+} // namespace isc

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

@@ -0,0 +1,327 @@
+// 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 <cstdlib>
+#include <string>
+#include <boost/lexical_cast.hpp>
+#include <log4cxx/logger.h>
+
+#include <log/dbglevels.h>
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+class Logger {
+public:
+
+    /// \brief Severity Levels
+    typedef enum {
+        DEFAULT,    // Default to logging level of parent
+        DEBUG,
+        INFO,
+        WARN,
+        ERROR,
+        FATAL,
+        NONE        // Disable logging
+    } Severity;
+
+    /// \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
+    /// child of the root logger.
+    ///
+    /// \param exit_delete This argument is present to get round a bug in
+    /// log4cxx.  If a log4cxx logger is declared outside an execution unit, it
+    /// is not deleted until the program runs down.  At that point all such
+    /// objects - including internal log4cxx objects - are deleted.  However,
+    /// there seems to be a bug in log4cxx where the way that such objects are
+    /// destroyed causes a MutexException to be thrown (this is described in
+    /// https://issues.apache.org/jira/browse/LOGCXX-322).  As this only occurs
+    /// during program rundown, the issue is not serious - it just looks bad to
+    /// have the program crash instead of shut down cleanly.\n
+    /// \n
+    /// The original implementation of the isc::log::Logger had as a member a
+    /// log4cxx logger (actually a LoggerPtr).  If the isc:: Logger was declared
+    /// statically, when it was destroyed at the end of the program the internal
+    /// LoggerPtr was destroyed, which triggered the problem.  The problem did
+    /// not occur if the isc::log::Logger was created on the stack.  To get
+    /// round this, the internal LoggerPtr is now created dynamically.  The
+    /// exit_delete argument controls its destruction: if true, it is destroyed
+    /// in the ISC Logger destructor.  If false, it is not.\n
+    /// \n
+    /// When creating an isc::log::Logger on the stack, the argument should be
+    /// false (the default); when the Logger is destroyed, all the internal
+    /// log4cxx objects are destroyed.  As only the logger (and not the internal
+    /// log4cxx data structures are being destroyed), all is well.  However,
+    /// when creating the logger statically, the argument should be false.  This
+    /// means that the log4cxx objects are not destroyed at program rundown;
+    /// instead memory is reclaimed and files are closed when the process is
+    /// destroyed, something that does not trigger the bug.
+    Logger(const std::string& name, bool exit_delete = false) :
+        loggerptr_(), name_(name), exit_delete_(exit_delete)
+    {}
+
+
+    /// \brief Destructor
+    virtual ~Logger();
+
+
+    /// \brief Configure Options
+    ///
+    /// TEMPORARY: Pass in the command-line options to set the logging severity
+    /// for the root logger.  Future versions of the logger will get this
+    /// information from the configuration database.
+    ///
+    /// \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.
+    /// \param local_file If provided, the name of a message file to read in and
+    /// supersede one or more of the current messages.
+    static void runTimeInit(Severity severity = INFO, int dbglevel = 1,
+        const char* local_file = NULL);
+
+
+    /// \brief Get Name of Logger
+    ///
+    /// \return The full name of the logger (including the root name)
+    virtual std::string getName() {
+        return getLogger()->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() {
+        return getSeverityCommon(getLogger(), 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() {
+        return getSeverityCommon(getLogger(), true);
+    }
+
+
+    /// \brief Return DEBUG Level
+    ///
+    /// \return Current setting of debug level.  This is returned regardless of
+    /// whether the 
+    virtual int getDebugLevel();
+
+
+    /// \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) {
+        return (getLogger()->getEffectiveLevel()->toInt() <=
+            (log4cxx::Level::DEBUG_INT - dbglevel));
+    }
+
+
+    /// \brief Is INFO Enabled?
+    virtual bool isInfoEnabled() {
+        return (getLogger()->isInfoEnabled());
+    }
+
+
+    /// \brief Is WARNING Enabled?
+    virtual bool isWarnEnabled() {
+        return (getLogger()->isWarnEnabled());
+    }
+
+
+    /// \brief Is ERROR Enabled?
+    virtual bool isErrorEnabled() {
+        return (getLogger()->isErrorEnabled());
+    }
+
+
+    /// \brief Is FATAL Enabled?
+    virtual bool isFatalEnabled() {
+        return (getLogger()->isFatalEnabled());
+    }
+
+
+    /// \brief Output Debug Message
+    ///
+    /// \param dbglevel Debug level, ranging between 0 and 99.  Higher numbers
+    /// are used for more verbose output.
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void debug(int dbglevel, MessageID ident, ...);
+
+
+    /// \brief Output Informational Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void info(MessageID ident, ...);
+
+
+    /// \brief Output Warning Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void warn(MessageID ident, ...);
+
+
+    /// \brief Output Error Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void error(MessageID ident, ...);
+
+
+    /// \brief Output Fatal Message
+    ///
+    /// \param ident Message identification.
+    /// \param ... Optional arguments for the message.
+    void fatal(MessageID ident, ...);
+
+
+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_ != NULL);
+    }
+
+
+    /// \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;
+
+
+    /// \brief Initialize log4cxx Logger
+    ///
+    /// Creates the log4cxx logger used internally.  A function is provided for
+    /// this so that the creation does not take place when this Logger object
+    /// is created but when it is used.  As the latter occurs in executable
+    /// code but the former can occur during initialization, this order
+    /// guarantees that anything that is statically initialized has completed
+    /// its initialization by the time the logger is used.
+    void initLogger();
+
+
+    /// \brief Return log4cxx Logger
+    ///
+    /// Returns the log4cxx logger, initializing it if not already initialized.
+    ///
+    /// \return Loggerptr object
+    log4cxx::LoggerPtr& getLogger() {
+        if (loggerptr_ == NULL) {
+            initLogger();
+        }
+        return *loggerptr_;
+    }
+
+
+    /// \brief Read Local Message File
+    ///
+    /// Reads a local message file into the global dictionary, replacing any
+    /// definitions there.  Any messages found in the local file that do not
+    /// replace ones in the global dictionary are listed.
+    ///
+    /// \param file Local message file to be read.
+    static void readLocalMessageFile(const char* file);
+
+private:
+    // Note that loggerptr_ is a pointer to a LoggerPtr, which is itself a
+    // pointer to the underlying log4cxx logger.  This is due to the problems
+    // with memory deletion on program exit, explained in the comments for
+    // the "exit_delete" parameter in this class's constructor.
+
+    log4cxx::LoggerPtr*  loggerptr_;    ///< Pointer to the underlying logger
+    std::string          name_;         ///< Name of this logger]
+    bool                 exit_delete_;  ///< Delete loggerptr_ on exit?
+
+    // NOTE - THIS IS A PLACE HOLDER
+    static bool         init_;      ///< Set true when initialized
+};
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_H

+ 116 - 0
src/lib/log/logger_support.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$
+
+
+
+/// \brief Temporary Logger Support
+///
+/// Performs run-time initialization of the logger system.  In particular, it
+/// is passed information from the command line and:
+///
+/// a) Sets the severity of the messages being logged (and debug level if
+/// appropriate).
+/// b) Reads in the local message file is one has been supplied.
+///
+/// These functions will be replaced once the code has bneen written to obtain
+/// the logging parameters from the configuration database.
+
+#include <vector>
+
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <log/messagedef.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+#include <log/message_types.h>
+#include <log/root_logger_name.h>
+
+namespace isc {
+namespace log {
+
+using namespace std;
+
+// Declare a logger for the logging subsystem
+Logger logger("log");
+
+
+/// \brief Reads Local Message File
+///
+/// Reads the local message file into the global dictionary, overwriting
+/// existing messages.  If the file contained any message IDs not in the
+/// dictionary, they are listed in a warning message.
+///
+/// \param file Name of the local message file
+static void
+readLocalMessageFile(const char* file) {
+    
+    MessageDictionary* dictionary = MessageDictionary::globalDictionary();
+    MessageReader reader(dictionary);
+    try {
+        reader.readFile(file, MessageReader::REPLACE);
+
+        // File successfully read, list the duplicates
+        MessageReader::MessageIDCollection unknown = reader.getNotAdded();
+        for (MessageReader::MessageIDCollection::const_iterator
+            i = unknown.begin(); i != unknown.end(); ++i) {
+                logger.warn(MSG_IDNOTFND, (*i).c_str());
+        }
+    }
+    catch (MessageException& e) {
+        MessageID ident = e.id();
+        vector<MessageID> args = e.arguments();
+        switch (args.size()) {
+        case 0:
+            logger.error(ident);
+            break;
+
+        case 1:
+            logger.error(ident, args[0].c_str());
+            break;
+
+        default:    // 2 or more (2 should be the maximum)
+            logger.error(ident, args[0].c_str(), args[1].c_str());
+        }
+    }
+}
+
+/// Logger Run-Time Initialization
+
+void
+runTimeInit(Logger::Severity severity, int dbglevel, const char* file) {
+
+    // Create the application root logger.  This is the logger that has the
+    // name of the application (and is one level down from the log4cxx root
+    // logger).  All other loggers created in this application will be its
+    // child.
+    //
+    // The main purpose of the application root logger is to provide the root
+    // name in output message for all other loggers.
+    Logger logger(RootLoggerName::getName());
+
+    // Set the severity associated with it.  If no other logger has a severity,
+    // this will be the default.
+    logger.setSeverity(severity, dbglevel);
+
+    // Replace any messages with local ones (if given)
+    if (file) {
+        readLocalMessageFile(file);
+    }
+}
+
+} // namespace log
+} // namespace isc

+ 43 - 0
src/lib/log/logger_support.h

@@ -0,0 +1,43 @@
+// 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_SUPPORT_H
+#define __LOGGER_SUPPORT_H
+
+#include <log/logger.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Run-Time Initialization
+///
+/// This code will be used until the logger is fully integrated into the BIND-10
+/// configuration database.  It performs run-time initialization of th logger,
+/// in particular supplying run-time choices to it:
+///
+/// * The severity (and if applicable, debug level) at which to log
+/// * Name of a local message file, containing localisation of message text.
+///
+/// \param severity Severity at which to log
+/// \param dbglevel Debug severiy (ignored if "severity" is not "DEBUG")
+/// \param file Name of the local message file.
+void runTimeInit(Logger::Severity severity, int dbglevel, const char* file);
+
+} // namespace log
+} // namespace isc
+
+
+#endif // __LOGGER_SUPPORT_H

+ 116 - 0
src/lib/log/message_dictionary.cc

@@ -0,0 +1,116 @@
+// 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 <cstddef>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// (Virtual) Destructor
+
+MessageDictionary::~MessageDictionary() {
+}
+
+// Add message and note if ID already exists
+
+bool
+MessageDictionary::add(const MessageID& ident, const std::string& text) {
+    map<MessageID, string>::iterator i = dictionary_.find(ident);
+    bool not_found = (i == dictionary_.end());
+    if (not_found) {
+
+        // Message not already in the dictionary, so add it.
+        dictionary_[ident] = text;
+    }
+    
+    return (not_found);
+}
+
+// Add message and note if ID does not already exist
+
+bool
+MessageDictionary::replace(const MessageID& ident, const std::string& text) {
+    map<MessageID, string>::iterator i = dictionary_.find(ident);
+    bool found = (i != dictionary_.end());
+    if (found) {
+
+        // Exists, so replace it.
+        dictionary_[ident] = text;
+    }
+    
+    return (found);
+}
+
+// Load a set of messages
+
+vector<MessageID>
+MessageDictionary::load(const char* messages[]) {
+    vector<MessageID> duplicates;
+    int i = 0;
+    while (messages[i]) {
+
+        // ID present, so note it and point to text.
+        MessageID ident(messages[i++]);
+        if (messages[i]) {
+
+            // Text not null, note it and point to next ident. 
+            string text(messages[i++]);
+
+            // Add ID and text to message dictionary, noting if the ID was
+            // already present.
+            bool added = add(ident, text);
+            if (!added) {
+                duplicates.push_back(ident);
+            }
+        }
+    }
+    return duplicates;
+}
+
+// Return message text or blank string
+
+string
+MessageDictionary::getText(const MessageID& ident) const {
+    map<MessageID, string>::const_iterator i = dictionary_.find(ident);
+    if (i == dictionary_.end()) {
+        return string("");
+    }
+    else {
+        return i->second;
+    }
+}
+
+// Return global dictionary
+
+MessageDictionary*
+MessageDictionary::globalDictionary() {
+    static MessageDictionary* global = NULL;
+
+    if (global == NULL) {
+        global = new MessageDictionary();
+    }
+    return global;
+}
+
+
+
+
+} // namspace log
+} // namespace isc

+ 150 - 0
src/lib/log/message_dictionary.h

@@ -0,0 +1,150 @@
+// 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_DICTIONARY_H
+#define __MESSAGE_DICTIONARY_H
+
+#include <cstddef>
+#include <string>
+#include <map>
+#include <vector>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Dictionary
+///
+/// The message dictionary is a wrapper around a std::map object, and allows
+/// message text to be retrieved given the string identification.
+///
+/// Adding text occurs in two modes:
+///
+/// Through the "Add" method, ID/text mappings are added to the dictionary
+/// unless the ID already exists.  This is designed for use during program
+/// initialization, where a local message may supplant a compiled-in message.
+///
+/// Through the "Replace" method, ID/text mappings are added to the dictionary
+/// only if the ID already exists.  This is for use when a message file is
+/// supplied to replace messages provided with the program.
+///
+/// Although the class can be used stand-alone, it does supply a static method
+/// to return a particular instance - the "global" dictionary.
+
+class MessageDictionary {
+public:
+
+    // Default constructor and assignment operator are OK for this class
+
+    /// \brief Virtual Destructor
+    virtual ~MessageDictionary();
+
+    /// \brief Add Message
+    ///
+    /// Adds a message to the dictionary.  If the ID already exists, the ID is
+    /// added to the overflow vector.
+    ///
+    /// \param ident Identification of the message to add
+    /// \param text Message text
+    ///
+    /// \return true if the message was added to the dictionary, false if the
+    /// message existed and it was not added.
+    virtual bool add(const MessageID& ident, const std::string& text);
+
+
+    /// \brief Replace Message
+    ///
+    /// Replaces a message in the dictionary.  If the ID does not exist, it is
+    /// added to the overflow vector.
+    ///
+    /// \param ident Identification of the message to replace
+    /// \param text Message text
+    ///
+    /// \return true if the message was added to the dictionary, false if the
+    /// message did not exist and it was not added.
+    virtual bool replace(const MessageID& ident, const std::string& text);
+
+
+    /// \brief Load Dictionary
+    ///
+    /// Designed to be used during the initialization of programs, this
+    /// accepts a set of (ID, text) pairs as a one-dimensional array of
+    /// const char* and adds them to the dictionary.  The messages are added
+    /// using "Add".
+    ///
+    /// \param data null-terminated array of const char* alternating ID and
+    /// message text.  This should be an odd number of elements long, the last
+    /// elemnent being NULL.  If it is an even number of elements long, the
+    /// last ID is ignored.
+    ///
+    /// \return Vector of message IDs that were not loaded because an ID of the
+    /// same name already existing in the dictionary.  This vector may be
+    /// empty.
+    virtual std::vector<MessageID> load(const char* elements[]);
+
+
+    /// \brief Get Message Text
+    ///
+    /// Given an ID, retrieve associated message text.
+    ///
+    /// \param ident Message identification
+    ///
+    /// \return Text associated with message or empty string if the ID is not
+    /// recognised.  (Note: this precludes an ID being associated with an empty
+    /// string.)
+    virtual std::string getText(const MessageID& ident) const;
+
+
+    /// \brief Number of Items in Dictionary
+    ///
+    /// \return Number of items in the dictionary
+    virtual size_t size() const {
+        return dictionary_.size();
+    }
+
+
+    // Allow access to the internal map structure, but don't allow alteration.
+    typedef std::map<MessageID, std::string>::const_iterator const_iterator;
+
+
+    /// \brief Return begin() iterator of internal map
+    const_iterator begin() const {
+        return dictionary_.begin();
+    }
+
+
+    /// \brief Return end() iterator of internal map
+    const_iterator end() const {
+        return dictionary_.end();
+    }
+
+
+    /// \brief Return Global Dictionary
+    ///
+    /// Returns a pointer to the singleton global dictionary.
+    ///
+    /// \return Pointer to global dictionary.
+    static MessageDictionary* globalDictionary();
+
+private:
+    std::map<MessageID, std::string>  dictionary_;
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_DICTIONARY_H

+ 28 - 0
src/lib/log/message_exception.cc

@@ -0,0 +1,28 @@
+// 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$
+
+/// \brief Body of Virtual Destructor
+
+#include <log/message_exception.h>
+
+namespace isc {
+namespace log {
+
+MessageException::~MessageException() throw() {
+}
+
+} // namespace log
+} // namespace isc

+ 90 - 0
src/lib/log/message_exception.h

@@ -0,0 +1,90 @@
+// 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_EXCEPTION_H
+#define __MESSAGE_EXCEPTION_H
+
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <log/message_types.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Message Exception
+///
+/// Used in the message reader, this simple exception class allows a message
+/// code and its arguments to be encapsulated in an exception and thrown
+/// up the stack.
+
+class MessageException : public std::exception {
+public:
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    MessageException(MessageID id) : id_(id)
+    {}
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    /// \param arg1 First message argument
+    MessageException(MessageID id, const std::string& arg1) : id_(id)
+    {
+        args_.push_back(arg1);
+    }
+
+    /// \brief Constructor
+    ///
+    /// \param id Message identification
+    /// \param arg1 First message argument
+    /// \param arg2 Second message argument
+    MessageException(MessageID id, const std::string& arg1,
+        const std::string& arg2) : id_(id)
+    {
+        args_.push_back(arg1);
+        args_.push_back(arg2);
+    }
+
+    /// \brief Destructor
+    virtual ~MessageException() throw();
+
+    /// \brief Return Message ID
+    ///
+    /// \return Message identification
+    MessageID id() const {
+        return id_;
+    }
+
+    /// \brief Return Arguments
+    ///
+    /// \return Exception Arguments
+    std::vector<std::string> arguments() const {
+        return args_;
+    }
+
+private:
+    MessageID                   id_;        // Exception ID
+    std::vector<std::string>    args_;      // Exception arguments
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_EXCEPTION_H

+ 32 - 0
src/lib/log/message_initializer.cc

@@ -0,0 +1,32 @@
+// 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_dictionary.h>
+#include <log/message_initializer.h>
+
+namespace isc {
+namespace log {
+
+// Constructor.  Just retrieve the global dictionary and load the IDs and
+// associated text into it.
+
+MessageInitializer::MessageInitializer(const char* values[]) {
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+    global->load(values);
+}
+
+} // namespace log
+} // namespace isc

+ 63 - 0
src/lib/log/message_initializer.h

@@ -0,0 +1,63 @@
+// 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 __MESSAGEINITIALIZER_H
+#define __MESSAGEINITIALIZER_H
+
+#include <log/message_dictionary.h>
+
+namespace isc {
+namespace log {
+
+/// \brief Initialize Message Dictionary
+///
+/// This is a helper class to add a set of message IDs and associated text to
+/// the global dictionary.
+///
+/// It should be declared outside an execution unit and initialized with a
+/// an array of values, alternating identifier, associated text and ending with
+/// a NULL, e.g.
+///
+///     static const char* values[] = {
+///         "IDENT1", "message for ident 1",
+///         "IDENT2", "message for ident 2",
+///             :
+///         NULL
+///     };
+///     MessageDictionaryHelper xyz(values);
+///
+/// This will automatically add the message ID/text pairs to the global
+/// dictionary during initialization - all that is required is that the module
+/// containing the definition is included into the final executable.
+///
+/// Messages are added via the MessageDictionary::add() method, so any
+/// duplicates are stored in the the global dictionary's overflow vector whence
+/// they can be retrieved at run-time.
+
+class MessageInitializer {
+public:
+
+    /// \brief Constructor
+    ///
+    /// The only method in the class, this adds the array of values to the
+    /// global dictionary.
+    MessageInitializer(const char* values[]);
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGEINITIALIZER_H

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

@@ -0,0 +1,184 @@
+// 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 <errno.h>
+#include <string.h>
+
+#include <iostream>
+#include <fstream>
+
+#include <log/message_exception.h>
+#include <log/messagedef.h>
+#include <log/message_reader.h>
+#include <log/strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace log {
+
+// Virtual destructor.
+MessageReader::~MessageReader() {
+}
+
+
+// Read the file.
+
+void
+MessageReader::readFile(const string& file, MessageReader::Mode mode) {
+
+    // Ensure the non-added collection is empty: this object might be
+    // being reused.
+    not_added_.clear();
+
+    // Open the file
+    ifstream infile(file.c_str());
+    if (infile.fail()) {
+        throw MessageException(MSG_OPENIN, file, strerror(errno));
+    }
+
+    // Loop round reading it.
+    string line;
+    getline(infile, line);
+    while (infile.good()) {
+        processLine(line, mode);
+        getline(infile, line);
+    }
+
+    // Why did the loop terminate?
+    if (!infile.eof()) {
+        throw MessageException(MSG_READERR, file, strerror(errno));
+    }
+    infile.close();
+}
+
+// Parse a line of the file
+
+void
+MessageReader::processLine(const string& line, MessageReader::Mode mode) {
+
+    // Get rid of leading and trailing spaces
+    string text = isc::strutil::trim(line);
+
+    if (text.empty()) {
+        ;                           // Ignore blank lines
+
+    } else if ((text[0] == '#') || (text[0] == '+')) {
+        ;                           // Ignore comments or descriptions
+
+    } else if (text[0] == '$') {
+        parseDirective(text);       // Process directives
+
+    } else {
+        parseMessage(text, mode);   // Process other lines
+
+    }
+}
+
+// Process directive
+
+void
+MessageReader::parseDirective(const std::string& text) {
+
+    static string valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
+
+    // Regardless of what happens, all prefixes will be uppercase (as will
+    // all symbols).
+    string line = text;
+    isc::strutil::uppercase(line);
+    vector<string> tokens = isc::strutil::tokens(line);
+
+    // Only $PREFIX is recognised so far, so we'll handle it here.
+    if (tokens[0] != string("$PREFIX")) {
+        throw MessageException(MSG_UNRECDIR, tokens[0]);
+
+    } else if (tokens.size() < 2) {
+        throw MessageException(MSG_PRFNOARG);
+
+    } else if (tokens.size() > 2) {
+        throw MessageException(MSG_PRFEXTRARG);
+
+    }
+
+    // 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 or it starts with a digit.
+        throw MessageException(MSG_PRFINVARG, tokens[1]);
+    }
+
+    // All OK - unless the prefix has already been set.
+
+    if (prefix_.size() != 0) {
+        throw MessageException(MSG_DUPLPRFX);
+    }
+
+    // Prefix has not been set, so set it and return success.
+
+    prefix_ = tokens[1];
+}
+
+// Process message.  By the time this method is called, the line has been
+// stripped of leading and trailing spaces, and we believe that it is a line
+// defining a message.  The first token on the line is convered to uppercase
+// and becomes the message ID; the rest of the line is the message text.
+
+void
+MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
+
+    static string delimiters("\t\n ");   // Delimiters
+
+    // Look for the first delimiter.
+    size_t first_delim = text.find_first_of(delimiters);
+    if (first_delim == string::npos) {
+
+        // Just a single token in the line - this is not valid
+        throw MessageException(MSG_ONETOKEN, text);
+    }
+
+    // Extract the first token into the message ID
+    MessageID ident = text.substr(0, first_delim);
+
+    // Locate the start of the message text
+    size_t first_text = text.find_first_not_of(delimiters, first_delim);
+    if (first_text == string::npos) {
+
+        // ?? This happens if there are trailing delimiters, which should not
+        // occur as we have stripped trailing spaces off the line.  Just treat
+        // this as a single-token error for simplicity's sake.
+        throw MessageException(MSG_ONETOKEN, text);
+    }
+
+    // Add the result to the dictionary and to the non-added list if the add to
+    // the dictionary fails.
+    bool added;
+    if (mode == ADD) {
+        added = dictionary_->add(ident, text.substr(first_text));
+    }
+    else {
+        added = dictionary_->replace(ident, text.substr(first_text));
+    }
+    if (!added) {
+        not_added_.push_back(ident);
+    }
+}
+
+} // namespace log
+} // namespace isc

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

@@ -0,0 +1,175 @@
+// 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>
+
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+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 Read Mode
+    ///
+    /// If ADD, messages are added to the dictionary if the ID does not exist
+    /// there.  If it does, the ID is added to the dictionary's overflow
+    /// vector.
+    ///
+    /// If REPLACE, the dictionary is only modified if the message ID already
+    /// exists in it.  New message IDs are added to the overflow vector.
+    typedef enum {
+        ADD,
+        REPLACE
+    } Mode;
+
+    /// \brief Visible collection types
+    typedef std::vector<MessageID>   MessageIDCollection;
+
+    /// \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).
+    ///
+    /// \param dictionary Dictionary to which messages read read from the file
+    /// are added.  (This should be a local dictionary when the class is used in
+    /// the message compiler, and the global dictionary when used in a server.
+    /// The ownership of the dictionary object is not transferred - the caller
+    /// is responsible for managing the lifetime of the dictionary.
+    MessageReader(MessageDictionary* dictionary = NULL) :
+        dictionary_(dictionary)
+    {}
+
+
+    /// \brief Virtual Destructor
+    virtual ~MessageReader();
+
+
+    /// \brief Get Dictionary
+    ///
+    /// Returns the pointer to the dictionary object.  Note that ownership is
+    /// not transferred - the caller should not delete it.
+    ///
+    /// \return Pointer to current dictionary object
+    MessageDictionary* getDictionary() const {
+        return dictionary_;
+    }
+
+
+    /// \brief Set Dictionary
+    ///
+    /// Sets the current dictionary object.
+    ///
+    /// \param dictionary New dictionary object. The ownership of the dictionary
+    /// object is not transferred - the caller is responsible for managing the
+    /// lifetime of the dictionary.
+    void setDictionary(MessageDictionary* dictionary) {
+        dictionary_ = dictionary;
+    }
+
+
+    /// \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 dictionary.
+    ///
+    /// \param file Name of the message file.
+    /// \param mode Addition mode.  See the description of the "Mode" enum.
+    virtual void readFile(const std::string& file, Mode mode = ADD);
+
+
+    /// \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
+    /// \param mode If a message line, how to add the message to the dictionary.
+    virtual void processLine(const std::string& line, Mode mode = ADD);
+
+
+    /// \brief Get Prefix
+    ///
+    /// \return Argument to the $PREFIX directive (if present)
+    virtual std::string getPrefix() const {
+        return prefix_;
+    }
+
+
+    /// \brief Clear Prefix
+    ///
+    /// Clears the current prefix.
+    virtual void clearPrefix() {
+        prefix_ = "";
+    }
+
+
+    /// \brief Get Not-Added List
+    ///
+    /// Returns the list of IDs that were not added during the last
+    /// read of the file.
+    ///
+    /// \return Collection of messages not added
+    MessageIDCollection getNotAdded() const {
+        return not_added_;
+    }
+
+private:
+
+    /// \brief Handle a Message Definition
+    ///
+    /// Passed a line that should contain a message, this processes that line
+    /// and adds it to the dictionary according to the mode setting.
+    ///
+    /// \param line Line of text
+    /// \param ADD or REPLACE depending on how the reader is operating.  (See
+    /// the description of the Mode typedef for details.)
+    void parseMessage(const std::string& line, Mode mode);
+
+
+    /// \brief Handle Directive
+    ///
+    /// Passed a line starting with a "$", this handles the processing of
+    /// directives.
+    ///
+    /// \param line Line of text that starts with "$",
+    void parseDirective(const std::string& line);
+
+    /// Attributes
+    MessageDictionary*  dictionary_;    ///< Dictionary to add messages to
+    MessageIDCollection not_added_;     ///< List of IDs not added
+    std::string         prefix_;        ///< Input of $PREFIX statement
+};
+
+} // namespace log
+} // namespace isc
+
+#endif // __MESSAGE_READER_H

+ 32 - 0
src/lib/log/message_types.h

@@ -0,0 +1,32 @@
+// 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_TYPES_H
+#define __MESSAGE_TYPES_H
+
+#include <string>
+
+namespace isc {
+namespace log {
+
+typedef std::string MessageID;
+
+} // namespace log
+} // namespace isc
+
+
+
+#endif // __MESSAGE_TYPES_H

+ 27 - 0
src/lib/log/messagedef.cc

@@ -0,0 +1,27 @@
+// File created from messagedef.mes on Mon Jan 17 15:25:32 2011
+
+#include <cstddef>
+#include <log/message_initializer.h>
+
+using namespace isc::log;
+
+namespace {
+
+const char* values[] = {
+    "DUPLPRFX", "duplicate $PREFIX directive found",
+    "IDNOTFND", "could not replace message for '%s': no such message identification",
+    "ONETOKEN", "a line containing a message ID ('%s') and nothing else was found",
+    "OPENIN", "unable to open message file %s for input: %s",
+    "OPENOUT", "unable to open %s for output: %s",
+    "PRFEXTRARG", "$PREFIX directive has too many arguments",
+    "PRFINVARG", "$PREFIX directive has an invalid argument ('%s')",
+    "PRFNOARG", "no arguments were given to the $PREFIX directive",
+    "READERR", "error reading from %s: %s",
+    "UNRECDIR", "unrecognised directive '%s'",
+    "WRITERR", "error writing to %s: %s",
+    NULL
+};
+
+} // Anonymous namespace
+
+MessageInitializer messagedef_cc_Mon_Jan_17_15_25_32_2011(values);

+ 24 - 0
src/lib/log/messagedef.h

@@ -0,0 +1,24 @@
+// File created from messagedef.mes on Mon Jan 17 15:25:32 2011
+
+#ifndef __MESSAGEDEF_H
+#define __MESSAGEDEF_H
+
+#include <log/message_types.h>
+
+namespace {
+
+isc::log::MessageID MSG_DUPLPRFX = "DUPLPRFX";
+isc::log::MessageID MSG_IDNOTFND = "IDNOTFND";
+isc::log::MessageID MSG_ONETOKEN = "ONETOKEN";
+isc::log::MessageID MSG_OPENIN = "OPENIN";
+isc::log::MessageID MSG_OPENOUT = "OPENOUT";
+isc::log::MessageID MSG_PRFEXTRARG = "PRFEXTRARG";
+isc::log::MessageID MSG_PRFINVARG = "PRFINVARG";
+isc::log::MessageID MSG_PRFNOARG = "PRFNOARG";
+isc::log::MessageID MSG_READERR = "READERR";
+isc::log::MessageID MSG_UNRECDIR = "UNRECDIR";
+isc::log::MessageID MSG_WRITERR = "WRITERR";
+
+} // Anonymous namespace
+
+#endif // __MESSAGEDEF_H

+ 82 - 0
src/lib/log/messagedef.mes

@@ -0,0 +1,82 @@
+# 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$
+
+$PREFIX MSG_
+
+# \brief Message Utility Message File
+#
+# This is the source of the set of messages generated by the message and logging
+# components.  The associated .h and .cc files are created by hand from this
+# file though and are not built during the build process; this is to avoid the
+# chicken-and-egg situation where we need the files to build the message
+# compiler, yet we need the compiler to build the files.
+
+DUPLPRFX    duplicate $PREFIX directive found
++ When reading a message file, more than one $PREFIX directive was found.  In
++ this version of the code, such a condition is regarded as an error and the
++ read will be abandonded.
+
+IDNOTFND    could not replace message for '%s': no such message identification
++ During start-up a local message file was read.  A line with the listed
++ message identification was found in the file, but the identification is not
++ one contained in the compiled-in message dictionary.  Either the message
++ identification has been mis-spelled in the file, or the local file was used
++ for an earlier version of the software and the message with that
++ identification has been removed.
++
++ This message may appear a number of times in the file, once for every such
++ unknown mnessage identification.
+
+ONETOKEN    a line containing a message ID ('%s') and nothing else was found
++ Message definitions comprise lines starting with a message identification (a
++ symbolic name for the message) and followed by the text of the message.  This
++ error is generated when a line is found in the message file that contains just
++ the message identification.
+
+OPENIN      unable to open message file %s for input: %s
++ The program was not able to open the specified input message file for the
++ reason given.
+
+OPENOUT     unable to open %s for output: %s
++ The program was not able to open the specified output file for the reason
++ given.
+
+PRFEXTRARG  $PREFIX directive has too many arguments
++ The $PREFIX directive takes a single argument, a prefix to be added to the
++ symbol names when a C++ .h file is created.  This error is generated when the
++ compiler finds a $PREFIX directive with more than one argument.
+
+PRFINVARG   $PREFIX directive has an invalid argument ('%s')
++ The $PREFIX argument is used in a symbol name in a C++ header file.  As such,
++ it must adhere to restrictions on C++ symbol names (e.g. may only contain
++ alphanumeric characters or underscores, and may nor start with a digit).  A
++ $PREFIX directive was found with an argument (given in the message) that
++ violates those restictions.
+
+PRFNOARG    no arguments were given to the $PREFIX directive
++ The $PREFIX directive takes a single argument, a prefix to be added to the
++ symbol names when a C++ .h file is created.  This error is generated when the
++ compiler finds a $PREFIX directive with noa rguments.
+
+READERR     error reading from %s: %s
++ The specified error was encountered reading from the named input file.
+
+UNRECDIR    unrecognised directive '%s'
++ A line starting with a dollar symbol was found, but the first word on the line
++ (shown in the message) was not a recognised message compiler directive.
+
+WRITERR     error writing to %s: %s
++ The specified error was encountered writing to the named output file.

+ 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_("");
+
+}
+}

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

@@ -0,0 +1,66 @@
+// 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 Constructor
+    ///
+    /// Sets the root logger name.  Although the name is static, setting the
+    /// name in the constructor allows static initialization of the name by
+    /// declaring an external instance of the class in the main execution unit.
+    RootLoggerName(const std::string& name) {
+        setName(name);
+    } 
+
+    /// \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

+ 138 - 0
src/lib/log/strutil.cc

@@ -0,0 +1,138 @@
+// 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 <numeric>
+#include <iostream>
+
+#include <string.h>
+#include <strutil.h>
+
+using namespace std;
+
+namespace isc {
+namespace strutil {
+
+// Normalize slashes
+
+void
+normalizeSlash(std::string& name) {
+    if (!name.empty()) {
+        size_t pos = 0;
+        while ((pos = name.find('\\', pos)) != std::string::npos) {
+            name[pos] = '/';
+        }
+    }
+}
+
+// Trim String
+
+string
+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>
+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;
+}
+
+// Local function to pass to accumulate() for summing up string lengths.
+
+namespace {
+
+size_t
+lengthSum(string::size_type curlen, const string& cur_string) {
+    return (curlen + cur_string.size());
+}
+
+}
+
+// Provide printf-style formatting.
+
+std::string
+format(const std::string& format, const std::vector<std::string>& args) {
+
+    static const string flag = "%s";
+
+    // Initialize return string.  To speed things up, we'll reserve an
+    // appropriate amount of space - current string size, plus length of all
+    // the argument strings, less two characters for each argument (the %s in
+    // the format string is being replaced).
+    string result;
+    size_t length = accumulate(args.begin(), args.end(), format.size(),
+        lengthSum) - (args.size() * flag.size());
+    result.reserve(length);
+
+    // Iterate through replacing all tokens
+    result = format;
+    size_t tokenpos = 0;    // Position of last token replaced
+    int i = 0;              // Index into argument array
+
+    while ((i < args.size()) && (tokenpos != string::npos)) {
+        tokenpos = result.find(flag, tokenpos);
+        if (tokenpos != string::npos) {
+            result.replace(tokenpos, flag.size(), args[i++]);
+        }
+    }
+
+    return result;
+}
+
+} // namespace log
+} // namespace isc

+ 147 - 0
src/lib/log/strutil.h

@@ -0,0 +1,147 @@
+// 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 __STRUTIL_H
+#define __STRUTIL_H
+
+#include <algorithm>
+#include <cctype>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace strutil {
+
+/// \brief A Set of C++ Utilities for Manipulating Strings
+
+/// \brief Normalize Backslash
+///
+/// Only relevant to 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
+void normalizeSlash(std::string& name);
+
+
+/// \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
+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.
+///
+/// Special cases are:
+/// -# The empty string is considered to be zero tokens.
+/// -# A string comprising nothing but delimiters is considered to be zero
+///    tokens.
+///
+/// The reasoning behind this is that the string can be thought of as having
+/// invisible leading and trailing delimiter characters.  Therefore both cases
+/// reduce to a set of contiguous delimiters, which are considered a single
+/// delimiter (so getting rid of the string).
+///
+/// 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.
+std::vector<std::string> tokens(const std::string text,
+        const std::string& delim = std::string(" \t\n"));
+
+
+/// \brief Uppercase Character
+///
+/// Used in uppercase() to pass as an argument to std::transform().  The
+/// function std::toupper() can't be used as it takes an "int" as its argument;
+/// this confuses the template expansion mechanism because defererencing a
+/// string::iterator returns a char.
+///
+/// \param chr Character to be upper-cased.
+///
+/// \return Uppercase version of the argument
+inline 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.
+inline void uppercase(std::string& text) {
+    std::transform(text.begin(), text.end(), text.begin(),
+        isc::strutil::toUpper);
+}
+
+/// \brief Lowercase Character
+///
+/// Used in lowercase() to pass as an argument to std::transform().  The
+/// function std::tolower() can't be used as it takes an "int" as its argument;
+/// this confuses the template expansion mechanism because defererencing a
+/// string::iterator returns a char.
+///
+/// \param chr Character to be lower-cased.
+///
+/// \return Lowercase version of the argument
+inline 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.
+inline void lowercase(std::string& text) {
+    std::transform(text.begin(), text.end(), text.begin(),
+        isc::strutil::toLower);
+}
+
+
+/// \brief Apply Formatting
+///
+/// Given a printf-style format string containing only "%s" place holders
+/// (others are ignored) and a vector of strings, this produces a single string
+/// with the placeholders replaced.
+///
+/// \param format Format string
+/// \param args Vector of argument strings
+///
+/// \return Resultant string
+std::string format(const std::string& format,
+    const std::vector<std::string>& args);
+
+
+} // namespace strutil
+} // namespace isc
+
+#endif // __STRUTIL_H

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

@@ -0,0 +1,45 @@
+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_dictionary_unittest.cc
+run_unittests_SOURCES += message_reader_unittest.cc
+run_unittests_SOURCES += message_initializer_unittest.cc
+run_unittests_SOURCES += message_initializer_unittest_2.cc
+run_unittests_SOURCES += strutil_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
+
+TESTS += logger_support_test
+logger_support_test_SOURCES = logger_support_test.cc
+logger_support_test_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+logger_support_test_LDFLAGS = $(AM_LDFLAGS)
+logger_support_test_LDADD  = $(top_builddir)/src/lib/log/liblog.la
+
+noinst_PROGRAMS = $(TESTS)
+
+# Additional test using the shell
+PYTESTS = run_time_init_test.sh
+check-local:
+	$(SHELL) $(abs_builddir)/run_time_init_test.sh

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

@@ -0,0 +1,181 @@
+// 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());
+
+    // 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());
+
+    // Empty string
+    fname.setName("");
+    EXPECT_EQ("", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ("", fname.extension());
+
+    // ... and just spaces
+    fname.setName("  ");
+    EXPECT_EQ("", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ("", fname.extension());
+
+    // Check corner cases - where separators are present, but strings are
+    // absent.
+    fname.setName("/");
+    EXPECT_EQ("/", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ("", fname.extension());
+
+    fname.setName(".");
+    EXPECT_EQ("", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ(".", fname.extension());
+
+    fname.setName("/.");
+    EXPECT_EQ("/", fname.directory());
+    EXPECT_EQ("", fname.name());
+    EXPECT_EQ(".", fname.extension());
+
+    // Note that the space is a valid filename here; only leading and trailing
+    // spaces should be trimmed.
+    fname.setName("/ .");
+    EXPECT_EQ("/", fname.directory());
+    EXPECT_EQ(" ", fname.name());
+    EXPECT_EQ(".", fname.extension());
+
+    fname.setName(" / . ");
+    EXPECT_EQ("/", fname.directory());
+    EXPECT_EQ(" ", 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(""));
+}

+ 23 - 0
src/lib/log/tests/localdef.mes

@@ -0,0 +1,23 @@
+# 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.
+
+# \brief Local Definitions
+#
+# Holds local definitions of some of the messages produced by the program
+# logger_support_test, and is used as input to check that run-time message
+# replacement works.
+
+NOTHERE     this message is not in the global dictionary
+READERR     replacement read error, parameters: '%s' and '%s'
+UNRECDIR    replacement unrecognised directive message, parameter is '%s'

+ 109 - 0
src/lib/log/tests/logger_support_test.cc

@@ -0,0 +1,109 @@
+// 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: $
+
+/// \brief Example Program
+///
+/// Simple example program showing how to use the logger.
+
+#include <unistd.h>
+#include <string.h>
+
+#include <iostream>
+
+#include <log/logger.h>
+#include <log/logger_support.h>
+#include <log/root_logger_name.h>
+
+// Include a set of message definitions.
+#include <log/messagedef.h>
+
+using namespace isc::log;
+
+// Declare root logger and a logger to use an example.
+//RootLoggerName root_name("testing");
+
+RootLoggerName root("alpha");
+Logger logger_ex("example");
+Logger logger_dlm("dlm");
+
+// The program is invoked:
+//
+// logger_support_test [-s severity] [-d level ] [local_file]
+//
+// "severity" is one of "debug", "info", "warn", "error", "fatal"
+// "level" is the debug level, a number between 0 and 99
+// "local_file" is the name of a local file.
+//
+// The program sets the attributes on the root logger.  Looking
+// at the output determines whether the program worked.e root logger.  Looking
+// at the output determines whether the 
+
+int main(int argc, char** argv) {
+
+    Logger::Severity    severity = Logger::INFO;
+    int                 dbglevel = -1;
+    const char*         localfile = NULL;
+    int                 option;
+
+    // Parse options
+    while ((option = getopt(argc, argv, "s:d:")) != -1) {
+        switch (option) {
+            case 's':
+                if (strcmp(optarg, "debug") == 0) {
+                    severity = Logger::DEBUG;
+                } else if (strcmp(optarg, "info") == 0) {
+                    severity = Logger::INFO;
+                } else if (strcmp(optarg, "warn") == 0) {
+                    severity = Logger::WARN;
+                } else if (strcmp(optarg, "error") == 0) {
+                    severity = Logger::ERROR;
+                } else if (strcmp(optarg, "fatal") == 0) {
+                    severity = Logger::FATAL;
+                } else {
+                    std::cout << "Unrecognised severity option: " <<
+                        optarg << "\n";
+                    exit(1);
+                }
+                break;
+
+            case 'd':
+                dbglevel = atoi(optarg);
+                break;
+
+            default:
+                std::cout << "Unrecognised option: " <<
+                    static_cast<char>(option) << "\n";
+        }
+    }
+
+    if (optind < argc) {
+        localfile = argv[optind];
+    }
+
+    // Update the logging parameters
+    runTimeInit(severity, dbglevel, localfile);
+
+    // Log a few messages
+    logger_ex.fatal(MSG_WRITERR, "test1", "42");
+    logger_ex.error(MSG_UNRECDIR, "false");
+    logger_dlm.warn(MSG_READERR, "a.txt", "dummy test");
+    logger_dlm.info(MSG_OPENIN, "example.msg", "dummy test");
+    logger_ex.debug(0, MSG_UNRECDIR, "[abc]");
+    logger_ex.debug(24, MSG_UNRECDIR, "[24]");
+    logger_ex.debug(25, MSG_UNRECDIR, "[25]");
+    logger_ex.debug(26, MSG_UNRECDIR, "[26]");
+    return 0;
+}

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

@@ -0,0 +1,395 @@
+// 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>
+#include <log/messagedef.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+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 string& name) : Logger(name, true)
+    {}
+
+    /// \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(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 string name1 = "alpha";
+    const string name2 = "beta";
+
+    // Instantiate two loggers that should be the same
+    TestLogger logger1(name1);
+    TestLogger logger2(name1);
+
+    // And check they are null at this point.
+    EXPECT_FALSE(logger1.isInitialized());
+    EXPECT_FALSE(logger2.isInitialized());
+
+    // Do some random operation
+    EXPECT_TRUE(logger1.isFatalEnabled());
+    EXPECT_TRUE(logger2.isFatalEnabled());
+
+    // And check they initialized and equal
+    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_FALSE(logger3.isInitialized());
+    EXPECT_TRUE(logger3.isFatalEnabled());
+    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::WARN, 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::FATAL, 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");
+    TestLogger logger("alpha");
+
+    // Now check the levels
+    logger.setSeverity(Logger::NONE);
+    EXPECT_EQ(Logger::NONE, logger.getSeverity());
+
+    logger.setSeverity(Logger::FATAL);
+    EXPECT_EQ(Logger::FATAL, logger.getSeverity());
+
+    logger.setSeverity(Logger::ERROR);
+    EXPECT_EQ(Logger::ERROR, logger.getSeverity());
+
+    logger.setSeverity(Logger::WARN);
+    EXPECT_EQ(Logger::WARN, 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");
+    TestLogger 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");
+    TestLogger parent("alpha");
+    TestLogger 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));
+}

+ 173 - 0
src/lib/log/tests/message_dictionary_unittest.cc

@@ -0,0 +1,173 @@
+// 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 <cstddef>
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_dictionary.h>
+#include <log/message_types.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class MessageDictionaryTest : public ::testing::Test {
+protected:
+    MessageDictionaryTest() : 
+        alpha_id("ALPHA"), alpha_text("This is alpha"),
+        beta_id("BETA"), beta_text("This is beta"),
+        gamma_id("GAMMA"), gamma_text("This is gamma")
+    {
+    }
+
+    MessageID alpha_id;
+    std::string alpha_text;
+    MessageID beta_id;
+    std::string beta_text;
+    MessageID gamma_id;
+    std::string gamma_text;
+
+};
+
+
+// Check that the global dictionary is a singleton.
+
+TEST_F(MessageDictionaryTest, GlobalTest) {
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+    EXPECT_FALSE(NULL == global);
+
+    MessageDictionary* global2 = MessageDictionary::globalDictionary();
+    EXPECT_EQ(global2, global);
+}
+
+// Check that adding messages works
+
+TEST_F(MessageDictionaryTest, Add) {
+    MessageDictionary dictionary;
+    EXPECT_EQ(0, dictionary.size());
+
+    // Add a few messages and check that we can look them up and that there is
+    // nothing in the overflow vector.
+    EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+    EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+    EXPECT_EQ(2, dictionary.size());
+
+    EXPECT_EQ(alpha_text, dictionary.getText(alpha_id));
+    EXPECT_EQ(beta_text, dictionary.getText(beta_id));
+    EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+
+    // Try adding a duplicate with different text.  It should not replace the
+    // current text and the ID should be in the overflow section.
+    EXPECT_FALSE(dictionary.add(alpha_id, gamma_text));
+    EXPECT_EQ(2, dictionary.size());
+}
+
+// Check that replacing messages works.
+
+TEST_F(MessageDictionaryTest, Replace) {
+    MessageDictionary dictionary;
+    EXPECT_EQ(0, dictionary.size());
+
+    // Try to replace a non-existent message
+    EXPECT_FALSE(dictionary.replace(alpha_id, alpha_text));
+    EXPECT_EQ(0, dictionary.size());
+
+    // Add a couple of messages.
+    EXPECT_TRUE(dictionary.add(alpha_id, alpha_text));
+    EXPECT_TRUE(dictionary.add(beta_id, beta_text));
+    EXPECT_EQ(2, dictionary.size());
+
+    // Replace an existing message
+    EXPECT_TRUE(dictionary.replace(alpha_id, gamma_text));
+    EXPECT_EQ(2, dictionary.size());
+    EXPECT_EQ(gamma_text, dictionary.getText(alpha_id));
+
+    // ... and replace non-existent message (but now the dictionary has some
+    // items in it).
+    EXPECT_FALSE(dictionary.replace(gamma_id, alpha_text));
+    EXPECT_EQ(2, dictionary.size());
+    EXPECT_EQ(string(""), dictionary.getText(gamma_id));
+}
+
+// Load test
+
+TEST_F(MessageDictionaryTest, LoadTest) {
+    static const char* data1[] = {
+        "ALPHA", "This is alpha",
+        "BETA", "This is beta",
+        "GAMMA", "This is gamma",
+        NULL
+    };
+
+    static const char* data2[] = {
+        "DELTA", "This is delta",
+        "EPSILON", "This is epsilon",
+        "ETA", NULL
+    };
+
+    MessageDictionary dictionary1;
+    EXPECT_EQ(0, dictionary1.size());
+
+    // Load a dictionary1.
+    vector<MessageID> duplicates = dictionary1.load(data1);
+    EXPECT_EQ(3, dictionary1.size());
+    EXPECT_EQ(string(data1[1]), dictionary1.getText(data1[0]));
+    EXPECT_EQ(string(data1[3]), dictionary1.getText(data1[2]));
+    EXPECT_EQ(string(data1[5]), dictionary1.getText(data1[4]));
+    EXPECT_EQ(0, duplicates.size());
+
+    // Attempt an overwrite
+    duplicates = dictionary1.load(data1);
+    EXPECT_EQ(3, dictionary1.size());
+    EXPECT_EQ(3, duplicates.size());
+
+    // Try a new dictionary but with an incorrect number of elements
+    MessageDictionary dictionary2;
+    EXPECT_EQ(0, dictionary2.size());
+
+    duplicates = dictionary2.load(data2);
+    EXPECT_EQ(2, dictionary2.size());
+    EXPECT_EQ(string(data2[1]), dictionary2.getText(data2[0]));
+    EXPECT_EQ(string(data2[3]), dictionary2.getText(data2[2]));
+    EXPECT_EQ(string(""), dictionary2.getText(data2[4]));
+    EXPECT_EQ(0, duplicates.size());
+}
+
+// Check for some non-existent items
+
+TEST_F(MessageDictionaryTest, Lookups) {
+    static const char* data[] = {
+        "ALPHA", "This is alpha",
+        "BETA", "This is beta",
+        "GAMMA", "This is gamma",
+        NULL
+    };
+
+    MessageDictionary dictionary;
+    vector<MessageID> duplicates = dictionary.load(data);
+    EXPECT_EQ(3, dictionary.size());
+    EXPECT_EQ(0, duplicates.size());
+
+    // Valid lookups
+    EXPECT_EQ(string("This is alpha"), dictionary.getText("ALPHA"));
+    EXPECT_EQ(string("This is beta"), dictionary.getText("BETA"));
+    EXPECT_EQ(string("This is gamma"), dictionary.getText("GAMMA"));
+
+    // ... and invalid ones
+    EXPECT_EQ(string(""), dictionary.getText("XYZZY"));
+    EXPECT_EQ(string(""), dictionary.getText(""));
+    EXPECT_EQ(string(""), dictionary.getText("\n\n\n"));
+}

+ 72 - 0
src/lib/log/tests/message_initializer_unittest.cc

@@ -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: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+#include <cstddef>
+#include <string>
+#include <gtest/gtest.h>
+#include <log/message_dictionary.h>
+#include <log/message_initializer.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+const char* values1[] = {
+    "GLOBAL1", "global message one",
+    "GLOBAL2", "global message two",
+    NULL
+};
+
+const char* values2[] = {
+    "GLOBAL3", "global message three",
+    "GLOBAL4", "global message four",
+    NULL
+};
+
+}
+
+// Statically initialize the global dictionary with those messages.  Three sets
+// are used to check that the declaration of separate initializer objects really// does combine the messages. (The third set is declared in the separately-
+// compiled file message_identifier_initializer_unittest_2.cc.)
+
+MessageInitializer init_message_initializer_unittest_1(values1);
+MessageInitializer init_message_initializer_unittest_2(values2);
+
+
+class MessageInitializerTest : public ::testing::Test {
+protected:
+    MessageInitializerTest()
+    {
+    }
+};
+
+
+// Check that the global dictionary is initialized with the specified
+// messages.
+
+TEST_F(MessageInitializerTest, MessageTest) {
+    MessageDictionary* global = MessageDictionary::globalDictionary();
+
+    EXPECT_EQ(string("global message one"), global->getText("GLOBAL1"));
+    EXPECT_EQ(string("global message two"), global->getText("GLOBAL2"));
+    EXPECT_EQ(string("global message three"), global->getText("GLOBAL3"));
+    EXPECT_EQ(string("global message four"), global->getText("GLOBAL4"));
+    EXPECT_EQ(string("global message five"), global->getText("GLOBAL5"));
+    EXPECT_EQ(string("global message six"), global->getText("GLOBAL6"));
+}

+ 41 - 0
src/lib/log/tests/message_initializer_unittest_2.cc

@@ -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: base64_unittest.cc 2549 2010-07-20 19:09:37Z jinmei $
+
+// The sole purpose of this file is to provide a set of message definitions
+// in a separate compilation unit from the one in which their presence is
+// checked.  This tests that merely declaring the MessageInitializer object
+// is enough to include the definitions in the global dictionary.
+
+#include <log/message_initializer.h>
+
+using namespace isc::log;
+
+// Declare a set of messages to go into the global dictionary.
+
+namespace {
+
+const char* values3[] = {
+    "GLOBAL5", "global message five",
+    "GLOBAL6", "global message six",
+    NULL
+};
+
+}
+
+// Statically initialize the global dictionary with those messages.
+// Three sets are used to check that the declaration of separate
+// initializer objects really does combine the messages.
+MessageInitializer init_message_initializer_unittest_3(values3);

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

@@ -0,0 +1,228 @@
+// 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 <algorithm>
+#include <string>
+#include <gtest/gtest.h>
+
+#include <log/messagedef.h>
+#include <log/message_dictionary.h>
+#include <log/message_exception.h>
+#include <log/message_reader.h>
+
+using namespace isc;
+using namespace isc::log;
+using namespace std;
+
+class MessageReaderTest : public ::testing::Test {
+protected:
+    MessageReaderTest() : dictionary_(), reader_()
+    {
+        dictionary_ = new MessageDictionary();
+        reader_.setDictionary(dictionary_);
+    }
+
+    ~MessageReaderTest() {
+        delete dictionary_;
+    }
+
+    MessageDictionary*  dictionary_;    // Dictionary to add messages to
+    MessageReader       reader_;        // Default reader object
+};
+
+
+// Check the get/set dictionary calls (using a local reader and dictionary).
+
+TEST_F(MessageReaderTest, GetSetDictionary) {
+    MessageReader reader;
+    EXPECT_TRUE(reader.getDictionary() == NULL);
+
+    MessageDictionary dictionary;
+    reader.setDictionary(&dictionary);
+    EXPECT_EQ(&dictionary, reader.getDictionary());
+}
+
+// Check for parsing blank lines and comments.  These should not add to the
+// dictionary and each parse should return success.
+
+TEST_F(MessageReaderTest, BlanksAndComments) {
+
+    // Ensure that the dictionary is empty.
+    EXPECT_EQ(0, dictionary_->size());
+
+    // Add a number of blank lines and comments and check that (a) they are
+    // parsed successfully ...
+    EXPECT_NO_THROW(reader_.processLine(""));
+    EXPECT_NO_THROW(reader_.processLine(" "));
+    EXPECT_NO_THROW(reader_.processLine(" \n "));
+    EXPECT_NO_THROW(reader_.processLine("# This is a comment"));
+    EXPECT_NO_THROW(reader_.processLine("\t\t # Another comment"));
+    EXPECT_NO_THROW(reader_.processLine("  + A description line"));
+    EXPECT_NO_THROW(reader_.processLine("#+ A comment"));
+    EXPECT_NO_THROW(reader_.processLine("  +# A description line"));
+
+    // ... and (b) nothing gets added to either the map or the not-added section.
+    EXPECT_EQ(0, dictionary_->size());
+    vector<MessageID> not_added = reader_.getNotAdded();
+    EXPECT_EQ(0, not_added.size());
+}
+
+
+// Local test to check that processLine generates the right exception.
+
+void
+processLineException(MessageReader& reader, const char* what,
+    MessageID& expected) {
+
+    try {
+        reader.processLine(what);
+        FAIL() << "MessageReader::processLine() should throw an exception " <<
+            " with message ID " << expected << " for '" << what << "'\n";
+    } catch (MessageException& e) {
+        EXPECT_EQ(expected, e.id());
+    } catch (...) {
+        FAIL() << "Unknown exception thrown by MessageReader::processLine()\n";
+    }
+}
+
+// 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.
+    processLineException(reader_, "$PREFIX", MSG_PRFNOARG);
+
+    // Check a prefix with multiple arguments is invalid
+    processLineException(reader_, "$prefix A B", MSG_PRFEXTRARG);
+
+    // Prefixes should be alphanumeric (with underscores) and not start
+    // with a number.
+    processLineException(reader_, "$prefix ab[cd", MSG_PRFINVARG);
+    processLineException(reader_, "$prefix 123", MSG_PRFINVARG);
+    processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
+
+    // A valid prefix should be accepted
+    EXPECT_NO_THROW(reader_.processLine("$PREFIX   dlm__"));
+    EXPECT_EQ(string("DLM__"), reader_.getPrefix());
+
+    // And check that the parser fails on invalid prefixes...
+    processLineException(reader_, "$prefix 1ABC", MSG_PRFINVARG);
+
+    // ... and rejects another valid one
+    processLineException(reader_, "$PREFIX ABC", MSG_DUPLPRFX);
+
+    // Check that we can clear the prefix as well
+    reader_.clearPrefix();
+    EXPECT_EQ(string(""), reader_.getPrefix());
+}
+
+// Check that it can parse a line
+
+TEST_F(MessageReaderTest, ValidMessageAddDefault) {
+
+    // Add a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n");
+    reader_.processLine("GLOBAL2 this is message global two");
+
+    // ... and check them
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // ... and ensure no messages were not added
+    vector<MessageID> not_added = reader_.getNotAdded();
+    EXPECT_EQ(0, not_added.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageAdd) {
+
+    // Add a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+        MessageReader::ADD);
+    reader_.processLine("GLOBAL2 this is message global two",
+        MessageReader::ADD);
+
+    // ... and check them
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // ... and ensure no messages were not added
+    vector<MessageID> not_added = reader_.getNotAdded();
+    EXPECT_EQ(0, not_added.size());
+}
+
+TEST_F(MessageReaderTest, ValidMessageReplace) {
+
+    dictionary_->add("GLOBAL1", "original global1 message");
+    dictionary_->add("GLOBAL2", "original global2 message");
+
+    // Replace a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n",
+        MessageReader::REPLACE);
+    reader_.processLine("GLOBAL2 this is message global two",
+        MessageReader::REPLACE);
+
+    // ... and check them
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // ... and ensure no messages were not added
+    vector<MessageID> not_added = reader_.getNotAdded();
+    EXPECT_EQ(0, not_added.size());
+}
+
+// Do checks on overflows, although this essentially duplicates the checks
+// in MessageDictionary.
+
+TEST_F(MessageReaderTest, Overflows) {
+
+    // Add a couple of valid messages
+    reader_.processLine("GLOBAL1\t\tthis is message global one\n");
+    reader_.processLine("GLOBAL2 this is message global two");
+
+    // Add a duplicate in ADD mode.
+    reader_.processLine("GLOBAL1\t\tthis is a replacement for global one");
+
+    // Replace a non-existent one in REPLACE mode
+    reader_.processLine("LOCAL\t\tthis is a new message",
+        MessageReader::REPLACE);
+
+    // Check what is in the dictionary.
+    EXPECT_EQ(string("this is message global one"),
+        dictionary_->getText("GLOBAL1"));
+    EXPECT_EQ(string("this is message global two"),
+        dictionary_->getText("GLOBAL2"));
+    EXPECT_EQ(2, dictionary_->size());
+
+    // ... and ensure no overflows
+    vector<MessageID> not_added = reader_.getNotAdded();
+    ASSERT_EQ(2, not_added.size());
+
+    sort(not_added.begin(), not_added.end());
+    EXPECT_EQ(string("GLOBAL1"), not_added[0]);
+    EXPECT_EQ(string("LOCAL"), not_added[1]);
+}

+ 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());
+}

+ 84 - 0
src/lib/log/tests/run_time_init_test.sh.in

@@ -0,0 +1,84 @@
+#!/bin/bash
+# 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$
+
+tempfile=`echo /tmp/run_init_test_$$`
+failcount=0
+localmes=@abs_builddir@/localdef.mes
+tempfile=@abs_builddir@/run_time_init_test_tempfile_$$
+
+function passfail() {
+    if [ $1 -eq 0 ]; then
+        echo "pass"
+    else
+        echo "FAIL"
+    fi
+    failcount=`expr $failcount + $1`
+}
+    
+echo -n "1. runInitTest default parameters: "
+cat > $tempfile << .
+FATAL [alpha.example] WRITERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+WARN  [alpha.dlm] READERR, error reading from a.txt: dummy test
+INFO  [alpha.dlm] OPENIN, unable to open message file example.msg for input: dummy test
+.
+./logger_support_test | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n "2. Severity filter: "
+cat > $tempfile << .
+FATAL [alpha.example] WRITERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+.
+./logger_support_test -s error | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n "3. Debug level: "
+cat > $tempfile << .
+FATAL [alpha.example] WRITERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, unrecognised directive 'false'
+WARN  [alpha.dlm] READERR, error reading from a.txt: dummy test
+INFO  [alpha.dlm] OPENIN, unable to open message file example.msg for input: dummy test
+DEBUG [alpha.example] UNRECDIR, unrecognised directive '[abc]'
+DEBUG [alpha.example] UNRECDIR, unrecognised directive '[24]'
+DEBUG [alpha.example] UNRECDIR, unrecognised directive '[25]'
+.
+./logger_support_test -s debug -d 25 | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n "4. Local message replacement: "
+cat > $tempfile << .
+WARN  [alpha.log] IDNOTFND, could not replace message for 'NOTHERE': no such message identification
+FATAL [alpha.example] WRITERR, error writing to test1: 42
+ERROR [alpha.example] UNRECDIR, replacement unrecognised directive message, parameter is 'false'
+WARN  [alpha.dlm] READERR, replacement read error, parameters: 'a.txt' and 'dummy test'
+INFO  [alpha.dlm] OPENIN, unable to open message file example.msg for input: dummy test
+.
+./logger_support_test $localmes | cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+rm -f $tempfile
+
+if [ $failcount -eq 0 ]; then
+    echo "PASS: run_time_init_test"
+elif [ $failcount -eq 1 ]; then
+    echo "FAIL: run_time_init_test - 1 test failed"
+else
+    echo "FAIL: run_time_init_test - $failcount tests failed"
+fi
+
+exit $failcount

+ 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());
+}

+ 216 - 0
src/lib/log/tests/strutil_unittest.cc

@@ -0,0 +1,216 @@
+// 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/strutil.h>
+
+using namespace isc;
+using namespace std;
+
+class StringUtilTest : public ::testing::Test {
+protected:
+    StringUtilTest()
+    {
+    }
+};
+
+
+// Check for slash replacement
+
+TEST_F(StringUtilTest, Slash) {
+
+    string instring = "";
+    isc::strutil::normalizeSlash(instring);
+    EXPECT_EQ("", instring);
+
+    instring = "C:\\A\\B\\C.D";
+    isc::strutil::normalizeSlash(instring);
+    EXPECT_EQ("C:/A/B/C.D", instring);
+
+    instring = "// \\ //";
+    isc::strutil::normalizeSlash(instring);
+    EXPECT_EQ("// / //", instring);
+}
+
+// Check that leading and trailing space trimming works
+
+TEST_F(StringUtilTest, Trim) {
+
+    // Empty and full string.
+    EXPECT_EQ("", isc::strutil::trim(""));
+    EXPECT_EQ("abcxyz", isc::strutil::trim("abcxyz"));
+
+    // Trim right-most blanks
+    EXPECT_EQ("ABC", isc::strutil::trim("ABC   "));
+    EXPECT_EQ("ABC", isc::strutil::trim("ABC\t\t  \n\t"));
+
+    // Left-most blank trimming
+    EXPECT_EQ("XYZ", isc::strutil::trim("  XYZ"));
+    EXPECT_EQ("XYZ", isc::strutil::trim("\t\t  \tXYZ"));
+
+    // Right and left, with embedded spaces
+    EXPECT_EQ("MN \t OP", isc::strutil::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
+
+    // Degenerate cases
+    result = isc::strutil::tokens("");          // Empty string
+    EXPECT_EQ(0, result.size());
+
+    result = isc::strutil::tokens(" \n ");      // String is all delimiters
+    EXPECT_EQ(0, result.size());
+
+    result = isc::strutil::tokens("abc");       // String has no delimiters
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+
+    // String containing leading and/or trailing delimiters, no embedded ones.
+    result = isc::strutil::tokens("\txyz");     // One leading delimiter
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = isc::strutil::tokens("\t \nxyz");  // Multiple leading delimiters
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = isc::strutil::tokens("xyz\n");     // One trailing delimiter
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = isc::strutil::tokens("xyz  \t");   // Multiple trailing
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    result = isc::strutil::tokens("\t xyz \n"); // Leading and trailing
+    ASSERT_EQ(1, result.size());
+    EXPECT_EQ(string("xyz"), result[0]);
+
+    // Embedded delimiters
+    result = isc::strutil::tokens("abc\ndef");  // 2 tokens, one separator
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+
+    result = isc::strutil::tokens("abc\t\t\ndef");  // 2 tokens, 3 separators
+    ASSERT_EQ(2, result.size());
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+
+    result = isc::strutil::tokens("abc\n  \tdef\t\tghi");
+    ASSERT_EQ(3, result.size());                // Multiple tokens, many delims
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+    EXPECT_EQ(string("ghi"), result[2]);
+
+    // Embedded and non-embedded delimiters
+
+    result = isc::strutil::tokens("\t\t  \nabc\n  \tdef\t\tghi   \n\n");
+    ASSERT_EQ(3, result.size());                // Multiple tokens, many delims
+    EXPECT_EQ(string("abc"), result[0]);
+    EXPECT_EQ(string("def"), result[1]);
+    EXPECT_EQ(string("ghi"), result[2]);
+
+    // Non-default delimiter
+    result = isc::strutil::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]);
+
+    // Non-default delimiters (plural)
+    result = isc::strutil::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;
+    isc::strutil::lowercase(test);
+    EXPECT_EQ(lower, test);
+
+    test = mixed;
+    isc::strutil::uppercase(test);
+    EXPECT_EQ(upper, test);
+}
+
+// Formatting
+
+TEST_F(StringUtilTest, Formatting) {
+
+    vector<string> args;
+    args.push_back("arg1");
+    args.push_back("arg2");
+    args.push_back("arg3");
+
+    string format1 = "This is a string with no tokens";
+    EXPECT_EQ(format1, isc::strutil::format(format1, args));
+
+    string format2 = "";    // Empty string
+    EXPECT_EQ(format2, isc::strutil::format(format2, args));
+
+    string format3 = "   ";    // Empty string
+    EXPECT_EQ(format3, isc::strutil::format(format3, args));
+
+    string format4 = "String with %d non-string tokens %lf";
+    EXPECT_EQ(format4, isc::strutil::format(format4, args));
+
+    string format5 = "String with %s correct %s number of tokens %s";
+    string result5 = "String with arg1 correct arg2 number of tokens arg3";
+    EXPECT_EQ(result5, isc::strutil::format(format5, args));
+
+    string format6 = "String with %s too %s few tokens";
+    string result6 = "String with arg1 too arg2 few tokens";
+    EXPECT_EQ(result6, isc::strutil::format(format6, args));
+
+    string format7 = "String with %s too %s many %s tokens %s !";
+    string result7 = "String with arg1 too arg2 many arg3 tokens %s !";
+    EXPECT_EQ(result7, isc::strutil::format(format7, args));
+
+    string format8 = "String with embedded%s%s%stokens";
+    string result8 = "String with embeddedarg1arg2arg3tokens";
+    EXPECT_EQ(result8, isc::strutil::format(format8, args));
+
+    // Handle an empty vector
+    args.clear();
+    string format9 = "%s %s";
+    EXPECT_EQ(format9, isc::strutil::format(format9, args));
+}

+ 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);
+}

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

@@ -0,0 +1,148 @@
+// 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 doesn't exist.
+    ///
+    /// \return Pointer to the desired logging level object.
+    static LevelPtr toLevelLS(const LogString& sArg,
+        const LevelPtr& defaultLevel);
+};
+
+} // namespace log4cxx
+
+
+#endif // __XDEBUGLEVEL_H