Commit cfa31a99 authored by Stephen Morris's avatar Stephen Morris
Browse files

[trac555] Checkpoint

Added basic logger manager and its implementation, plus an untested
console appender.  Also modified logger_support_test to use that
appender and have got part way through creating a file logger test.
parent fae2d0d5
......@@ -15,6 +15,7 @@ liblog_la_SOURCES += logger_level.h
liblog_la_SOURCES += logger_level.h
liblog_la_SOURCES += logger_level_impl.cc logger_level_impl.h
liblog_la_SOURCES += logger_manager.cc logger_manager.h
liblog_la_SOURCES += logger_manager_impl.cc logger_manager_impl.h
liblog_la_SOURCES += logger_specification.h
liblog_la_SOURCES += logger_support.cc logger_support.h
liblog_la_SOURCES += macros.h
......
......@@ -33,7 +33,7 @@
#include <util/strutil.h>
// Note: as log4cplus and th3e BIND 10 logger have many concepts in common, and
// Note: as log4cplus and the BIND 10 logger have many concepts in common, and
// thus many similar names, to disambiguate types we don't "use" the log4cplus
// namespace: instead, all log4cplus types are explicitly qualified.
......
......@@ -18,9 +18,7 @@
namespace isc {
namespace log {
void LoggerManagerImpl::processInit() {}
void LoggerManagerImpl::processEnd() {}
void LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {}
// Constructor - create the implementation class.
LoggerManager::LoggerManager() {
......
......@@ -56,6 +56,24 @@ public:
processEnd();
}
/// \brief Process a single specification
///
/// A convenience function for a single specification.
///
/// \param spec Specification to process
void process(const LoggerSpecification& spec) {
processInit();
processSpecification(spec);
processEnd();
}
/// \brief Initialization
///
/// Static method for initializing the whole of the logging system. This
/// must be called before anything else.
static void init();
private:
/// \brief Initialize Processing
///
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <log4cplus/logger.h>
#include <log4cplus/configurator.h>
#include <log4cplus/consoleappender.h>
#include "log/logger_level_impl.h"
#include "log/logger_manager_impl.h"
#include "log/logger_specification.h"
#include "log/root_logger_name.h"
#include "exceptions/exceptions.h"
// Generated exceptions. Methods in this file can't log exceptions as they may
// occur when logging is disabled or in an inconsistent state.
class UnknownLoggingDestination : public isc::Exception {
public:
UnknownLoggingDestination(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what)
{}
};
// Reset hierarchy of loggers back to default settings. This removes all
// appenders from loggers, sets their severity to NOT_SET (so that events are
// passed back to the parent) and resets the root logger to logging
// informational messages. (This last is not a log4cplus default, so we have to
// explicitly reset the logging severity.)
namespace isc {
namespace log {
// Reset hierarchy back to default. Note that this does not delete existing
// loggers, it makes them inactive. (So a logger is never removed, even if a
// configuration update removes the logger.)
void
LoggerManagerImpl::processInit() {
log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
log4cplus::Logger::getRoot().setLogLevel(log4cplus::INFO_LOG_LEVEL);
}
// Process logging specification. Set up the common states then dispatch to
// add output specifications.
void
LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
// Get/construct the logger for which this specification applies.
log4cplus::Logger logger = (spec.getName() == getRootLoggerName()) ?
log4cplus::Logger::getRoot() :
log4cplus::Logger::getInstance(spec.getName());
// Set severity level according to specification entry.
logger.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
Level(spec.getSeverity(), spec.getDbglevel())));
// Set the additive flag.
logger.setAdditivity(spec.getAdditive());
// Now process output specifications.
for (LoggerSpecification::const_iterator i = spec.begin();
i != spec.end(); ++i) {
switch (i->destination) {
case OutputOption::DEST_CONSOLE:
createConsoleAppender(logger, *i);
break;
case OutputOption::DEST_FILE:
createFileAppender(logger, *i);
break;
case OutputOption::DEST_SYSLOG:
createSyslogAppender(logger, *i);
break;
default:
isc_throw(UnknownLoggingDestination,
"Unknown logging destination, code = " << i->destination);
}
}
}
// Console appender - log to either stdout or stderr.
void
LoggerManagerImpl::createConsoleAppender(log4cplus::Logger& logger,
const OutputOption& opt)
{
log4cplus::SharedAppenderPtr console(
new log4cplus::ConsoleAppender(
(opt.stream == OutputOption::STR_STDERR), opt.flush));
logger.addAppender(console);
}
} // namespace log
} // namespace isc
......@@ -17,6 +17,11 @@
#include <log/logger_specification.h>
// Forward declaration to avoid need to include log4cplus header file here.
namespace log4cplus {
class Logger;
}
namespace isc {
namespace log {
......@@ -54,6 +59,38 @@ public:
///
/// Terminates the processing of the logging specifications.
void processEnd();
private:
/// \brief Create console appender
///
/// Creates an object that, when attached to a logger, will log to one
/// of the output streams (stdout or stderr).
///
/// \param logger Log4cplus logger to which the appender must be attached.
/// \param opt Output options for this appender.
void createConsoleAppender(log4cplus::Logger& logger,
const OutputOption& opt);
/// \brief Create file appender
///
/// Creates an object that, when attached to a logger, will log to a
/// specified file. This also includes the ability to "roll" files when
/// they reach a specified size.
///
/// \param logger Log4cplus logger to which the appender must be attached.
/// \param opt Output options for this appender.
void createFileAppender(log4cplus::Logger& logger,
const OutputOption& opt) {}
/// \brief Create syslog appender
///
/// Creates an object that, when attached to a logger, will log to the
/// syslog file.
///
/// \param logger Log4cplus logger to which the appender must be attached.
/// \param opt Output options for this appender.
void createSyslogAppender(log4cplus::Logger& logger,
const OutputOption& opt) {}
};
} // namespace log
......
......@@ -53,33 +53,33 @@ public:
additive_(additive)
{}
/// \brief Set the name
/// \brief Set the name of the logger.
///
/// \param name Name of the logger .
/// \param name Name of the logger.
void setName(const std::string& name) {
name_ = name;
}
/// \return Return logger name
/// \return Return logger name.
std::string getName() const {
return name_;
}
/// \brief Set the severity
/// \brief Set the severity.
///
/// \param severity New severity of the logger .
/// \param severity New severity of the logger.
void setSeverity(isc::log::Severity severity) {
severity_ = severity;
}
/// \return Return logger severity
/// \return Return logger severity.
isc::log::Severity getSeverity() const {
return severity_;
}
/// \brief Set the debug level
/// \brief Set the debug level.
///
/// \param dbglevel New debug level of the logger .
/// \param dbglevel New debug level of the logger.
void setDbglevel(int dbglevel) {
dbglevel_ = dbglevel;
}
......@@ -89,51 +89,51 @@ public:
return dbglevel_;
}
/// \brief Set the additive flag
/// \brief Set the additive flag.
///
/// \param additive New value of the additive flag
/// \param additive New value of the additive flag.
void setAdditive(bool additive) {
additive_ = additive;
}
/// \return Return additive flag
/// \return Return additive flag.
int getAdditive() const {
return additive_;
}
/// \brief Add output option
/// \brief Add output option.
///
/// \param Option to add to the list
/// \param Option to add to the list.
void addOutputOption(const OutputOption& option) {
options_.push_back(option);
}
/// \return Iterator to start of output options
/// \return Iterator to start of output options.
iterator begin() {
return options_.begin();
}
/// \return Iterator to start of output options
/// \return Iterator to start of output options.
const_iterator begin() const {
return options_.begin();
}
/// \return Iterator to end of output options
/// \return Iterator to end of output options.
iterator end() {
return options_.end();
}
/// \return Iterator to end of output options
/// \return Iterator to end of output options.
const_iterator end() const {
return options_.end();
}
/// \return Number of output specification options
/// \return Number of output specification options.
size_t optionCount() const {
return options_.size();
}
/// \brief Reset back to defaults
/// \brief Reset back to defaults.
void reset() {
name_ = "";
severity_ = isc::log::INFO;
......
......@@ -15,6 +15,8 @@
#ifndef __LOGGER_SUPPORT_H
#define __LOGGER_SUPPORT_H
#include <unistd.h>
#include <string>
#include <log/logger.h>
......@@ -36,8 +38,9 @@ namespace log {
/// \param severity Severity at which to log
/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
/// \param file Name of the local message file.
void initLogger(const std::string& root, isc::log::Severity severity,
int dbglevel, const char* file);
void initLogger(const std::string& root,
isc::log::Severity severity = isc::log::INFO,
int dbglevel = 0, const char* file = NULL);
/// \brief Run-Time Initialization from Environment
......
......@@ -33,6 +33,7 @@ run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CPLUS_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
TESTS += logger_support_test
......
......@@ -21,6 +21,10 @@
#include <gtest/gtest.h>
#include <boost/scoped_array.hpp>
#include <exceptions/exceptions.h>
#include <log/macros.h>
#include <log/messagedef.h>
#include <log/logger.h>
......@@ -29,6 +33,7 @@
#include <log/logger_specification.h>
#include <log/output_option.h>
using namespace isc;
using namespace isc::log;
using namespace std;
......@@ -41,8 +46,6 @@ class DerivedLogger : public isc::log::Logger {
public:
DerivedLogger(std::string name) : isc::log::Logger(name)
{}
virtual ~DerivedLogger()
{}
static void reset() {
isc::log::Logger::reset();
......@@ -70,10 +73,14 @@ public:
// Constructor - allocate file and create the specification object
SpecificationForFileLogger() : spec_(), name_(""), logname_("filelogger") {
// Set the output to a temporary file.
OutputOption option;
option.destination = OutputOption::DEST_FILE;
name_ = option.filename = std::string(tmpnam(NULL));
option.filename = name_ = createTempFilename();
// Set target output to the file logger. The defauls indicate
// INFO severity.
spec_.setName(logname_);
spec_.addOutputOption(option);
}
......@@ -100,6 +107,48 @@ public:
return name_;
}
// Create temporary filename
//
// The compiler warns against tmpnam() and suggests mkstemp instead.
// Unfortunately, this creates the filename and opens it. So we need to
// close and delete the file before returning the name. Also, the name
// is based on the template supplied and the name of the temporary
// directory may vary between systems. So translate TMPDIR and if that
// does not exist, use /tmp.
//
// \return Temporary file name
std::string createTempFilename() {
// Get prefix. Note that in all copies, strncpy does not guarantee
// a null-terminated string, hence the explict setting of the last
// character to NULL.
ostringstream filename;
if (getenv("TMPDIR") != NULL) {
filename << getenv("TMPDIR");
} else {
filename << "/tmp";
}
filename << "/bind10_logger_manager_test_XXXXXX";
cout << "*** file name before call is " << filename.str() << "\n";
// Copy into writeable storage for the call to mkstemp
boost::scoped_array<char> tname(new char[filename.str().size() + 1]);
strcpy(tname.get(), filename.str().c_str());
// Create file, close and delete it, and store the name for later.
int filenum = mkstemp(tname.get());
cout << "*** file name after call is " << tname.get() << "\n";
if (filenum == -1) {
isc_throw(Exception, "Unable to obtain unique filename");
}
close(filenum);
unlink(tname.get());
return (string(tname.get()));
}
private:
LoggerSpecification spec_; // Specification for this file logger
......@@ -153,7 +202,7 @@ void checkFileContents(const std::string& filename, T start, T finish) {
}
// Check that the logger correctly creates something logging to a file.
TEST_F(LoggerManagerTest, DISABLED_FileLogger) {
TEST_F(LoggerManagerTest, FileLogger) {
// Create a specification for the file logger and use the manager to
// connect the "filelogger" logger to it.
......
......@@ -20,68 +20,124 @@
#include <unistd.h>
#include <string.h>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <string>
#include <log/logger.h>
#include <log/macros.h>
#include <log/logger_manager.h>
#include <log/logger_specification.h>
#include <log/logger_support.h>
#include <log/macros.h>
#include <log/root_logger_name.h>
// Include a set of message definitions.
#include <log/messagedef.h>
using namespace isc::log;
using namespace std;
// Print usage information
void usage() {
cout <<
"logger_support_test [-h] [-s severity] [-d dbglevel] [-c stream] [localfile]\n"
"\n"
" -h Print this message and exit\n"
" -s severity Set the severity of messages output. 'severity' is one\n"
" of 'debug', 'info', 'warn', 'error', 'fatal', the default\n"
" being 'info'.\n"
" -d dbglevel Debug level. Only interpreted if the severity is 'debug'\n"
" this is a number between 0 and 99.\n"
" -c stream Send output to the console. 'stream' is one of 'stdout'\n"
" of 'stderr'. The '-c' switch is incompatible with '-f'\n"
" and '-l'\n"
"\n"
"If none of -c, -f or -l is given, by default, output is sent to stdout\n";
}
// Declare logger to use an example.
Logger logger_ex("example");
// The program is invoked:
//
// logger_support_test [-s severity] [-d level ] [local_file]
//
// "severity" is one of "debug", "info", "warn", "error", "fatal"
// "level" is the debug level, a number between 0 and 99
// "local_file" is the name of a local file.
//
// The program sets the attributes on the root logger and logs a set of
// messages. Looking at the output determines whether the program worked.
int main(int argc, char** argv) {
const string ROOT_NAME = "alpha";
isc::log::Severity severity = isc::log::INFO; // Default logger severity
int dbglevel = -1; // Logger debug level
const char* localfile = NULL; // Local message file
int option; // For getopt() processing
Logger logger_dlm("dlm"); // Another example logger
bool c_found = false; // Set true if "-c" found
bool f_found = false; // Set true if "-f" found
bool l_found = false; // Set true if "-l" found
const char* localfile = NULL; // Local message file
int option; // For getopt() processing
LoggerSpecification spec(ROOT_NAME); // Logger specification
OutputOption outopt; // Logger output option
// Initialize loggers (to set the root name and initialize logging);
// We'll reset them later.
setRootLoggerName(ROOT_NAME);
Logger rootLogger(ROOT_NAME);
// Parse options
while ((option = getopt(argc, argv, "s:d:")) != -1) {
while ((option = getopt(argc, argv, "hc:d:s:")) != -1) {
switch (option) {
case 's':
if (strcmp(optarg, "debug") == 0) {
severity = isc::log::DEBUG;
} else if (strcmp(optarg, "info") == 0) {
severity = isc::log::INFO;
} else if (strcmp(optarg, "warn") == 0) {
severity = isc::log::WARN;
} else if (strcmp(optarg, "error") == 0) {
severity = isc::log::ERROR;
} else if (strcmp(optarg, "fatal") == 0) {
severity = isc::log::FATAL;
} else {
std::cout << "Unrecognised severity option: " <<
optarg << "\n";
exit(1);
}
break;
case 'd':
dbglevel = atoi(optarg);
break;
default:
std::cout << "Unrecognised option: " <<
static_cast<char>(option) << "\n";
case 'c':
if (f_found || l_found) {
cerr << "Cannot specify -c with -f or -l\n";
return (1);
}
c_found = true;
outopt.destination = OutputOption::DEST_CONSOLE;
if (strcmp(optarg, "stdout") == 0) {
outopt.stream = OutputOption::STR_STDOUT;
} else if (strcmp(optarg, "stderr") == 0) {
outopt.stream = OutputOption::STR_STDERR;
} else {
cerr << "Unrecognised console option: " << optarg << "\n";
return (1);
}
break;
case 'd':
spec.setDbglevel(boost::lexical_cast<int>(optarg));
break;
case 'h':
usage();
return (0);
case 's':
if (strcmp(optarg, "debug") == 0) {
spec.setSeverity(isc::log::DEBUG);
} else if (strcmp(optarg, "info") == 0) {
spec.setSeverity(isc::log::INFO);
} else if (strcmp(optarg, "warn") == 0) {
spec.setSeverity(isc::log::WARN);
} else if (strcmp(optarg, "error") == 0) {
spec.setSeverity(isc::log::ERROR);
} else if (strcmp(optarg, "fatal") == 0) {
spec.setSeverity(isc::log::FATAL);
} else {
cerr << "Unrecognised severity option: " << optarg << "\n";
return (1);
}
break;
default:
std::cerr << "Unrecognised option: " <<
static_cast<char>(option) << "\n";
return (1);
}
}
......@@ -90,9 +146,23 @@ int main(int argc, char** argv) {
}