diff --git a/src/bin/d2/d_controller.cc b/src/bin/d2/d_controller.cc index 39acdefe04..d033e1025b 100644 --- a/src/bin/d2/d_controller.cc +++ b/src/bin/d2/d_controller.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -217,6 +218,28 @@ DControllerBase::configFromFile() { isc::data::ConstElementPtr whole_config = isc::data::Element::fromJSONFile(config_file, true); + // Let's configure logging before applying the configuration, + // so we can log things during configuration process. + + // Temporary storage for logging configuration + isc::dhcp::ConfigurationPtr storage(new isc::dhcp::Configuration()); + + // Get 'Logging' element from the config + isc::data::ConstElementPtr logger = whole_config->get("Logging"); + if (logger) { + // Configure logger first, so it can be applied to DHCPv6 + // configuration. If we don't have a logger, just pass + // empty configuration. + + Daemon::configureLogger(logger, storage); + } else { + // There was no Logging element defined in the config file. + // Let's pass an empty pointer that will remove any current + // configuration. + Daemon::configureLogger(isc::data::ConstElementPtr(), + storage); + } + // Extract derivation-specific portion of the configuration. module_config = whole_config->get(getAppName()); if (!module_config) { diff --git a/src/bin/dhcp4/kea_controller.cc b/src/bin/dhcp4/kea_controller.cc index 017bad5f72..2671494f81 100644 --- a/src/bin/dhcp4/kea_controller.cc +++ b/src/bin/dhcp4/kea_controller.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,7 @@ void configure(const std::string& file_name) { isc::data::ConstElementPtr json; isc::data::ConstElementPtr dhcp4; + isc::data::ConstElementPtr logger; isc::data::ConstElementPtr result; // Basic sanity check: file name must not be empty. @@ -62,6 +64,23 @@ void configure(const std::string& file_name) { " file: " << file_name); } + // Let's configure logging before applying the configuration, + // so we can log things during configuration process. + logger = json->get("Logging"); + if (logger) { + // Configure logger first, so it can be applied to DHCPv6 + // configuration. If we don't have a logger, just pass + // empty configuration. + + Daemon::configureLogger(logger, CfgMgr::instance().getConfiguration()); + } else { + // There was no Logging element defined in the config file. + // Let's pass an empty pointer that will remove any current + // configuration. + Daemon::configureLogger(isc::data::ConstElementPtr(), + CfgMgr::instance().getConfiguration()); + } + // Get Dhcp4 component from the config dhcp4 = json->get("Dhcp4"); diff --git a/src/bin/dhcp6/kea_controller.cc b/src/bin/dhcp6/kea_controller.cc index c70cf3bf60..c346246882 100644 --- a/src/bin/dhcp6/kea_controller.cc +++ b/src/bin/dhcp6/kea_controller.cc @@ -47,6 +47,7 @@ void configure(const std::string& file_name) { isc::data::ConstElementPtr json; isc::data::ConstElementPtr dhcp6; + isc::data::ConstElementPtr logger; isc::data::ConstElementPtr result; // Basic sanity check: file name must not be empty. @@ -67,6 +68,22 @@ void configure(const std::string& file_name) { + file_name); } + // Let's configure logging before applying the configuration, + // so we can log things during configuration process. + logger = json->get("Logging"); + if (logger) { + // Configure logger first, so it can be applied to DHCPv6 + // configuration. If we don't have a logger, just pass + // empty configuration. + Daemon::configureLogger(logger, CfgMgr::instance().getConfiguration()); + } else { + // There was no Logging element defined in the config file. + // Let's pass an empty pointer that will remove any current + // configuration. + Daemon::configureLogger(isc::data::ConstElementPtr(), + CfgMgr::instance().getConfiguration()); + } + // Get Dhcp6 component from the config dhcp6 = json->get("Dhcp6"); diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 6044edcf3e..000bad32bd 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -59,6 +59,8 @@ libkea_dhcpsrv_la_SOURCES += key_from_key.h libkea_dhcpsrv_la_SOURCES += lease.cc lease.h libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h libkea_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h +libkea_dhcpsrv_la_SOURCES += logging.cc logging.h +libkea_dhcpsrv_la_SOURCES += configuration.h libkea_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h if HAVE_MYSQL diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc index 12f1e5bf98..bc2cbf9a9d 100644 --- a/src/lib/dhcpsrv/cfgmgr.cc +++ b/src/lib/dhcpsrv/cfgmgr.cc @@ -437,10 +437,15 @@ CfgMgr::getD2ClientMgr() { return (d2_client_mgr_); } +ConfigurationPtr +CfgMgr::getConfiguration() { + return (configuration_); +} + CfgMgr::CfgMgr() : datadir_(DHCP_DATA_DIR), all_ifaces_active_(false), echo_v4_client_id_(true), - d2_client_mgr_() { + d2_client_mgr_(), configuration_(new Configuration()) { // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am // Note: the definition of DHCP_DATA_DIR needs to include quotation marks // See AM_CPPFLAGS definition in Makefile.am diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h index 3331135b7d..b572dd8265 100644 --- a/src/lib/dhcpsrv/cfgmgr.h +++ b/src/lib/dhcpsrv/cfgmgr.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -420,6 +421,12 @@ public: /// @return a reference to the DHCP-DDNS manager. D2ClientMgr& getD2ClientMgr(); + + /// @brief Returns the current configuration. + /// + /// @return a pointer to the current configuration. + ConfigurationPtr getConfiguration(); + protected: /// @brief Protected constructor. @@ -516,6 +523,14 @@ private: /// @brief Manages the DHCP-DDNS client and its configuration. D2ClientMgr d2_client_mgr_; + + /// @brief Configuration + /// + /// This is a structure that will hold all configuration. + /// @todo: migrate all other parameters to that structure. + /// @todo: maybe this should be a vector, so we could keep + /// previous configurations and do a rollback if needed? + ConfigurationPtr configuration_; }; } // namespace isc::dhcp diff --git a/src/lib/dhcpsrv/configuration.h b/src/lib/dhcpsrv/configuration.h new file mode 100644 index 0000000000..348e4f6866 --- /dev/null +++ b/src/lib/dhcpsrv/configuration.h @@ -0,0 +1,94 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef DHCPSRV_CONFIG_H +#define DHCPSRV_CONFIG_H + +#include +#include + +namespace isc { +namespace dhcp { + + +/// @brief Defines single logging destination +/// +/// This structure is used to keep log4cplus configuration parameters. +struct LoggingDestination { + + /// @brief defines logging destination output + /// + /// Values accepted are: stdout, stderr, syslog, syslog:name. + /// Any other destination will be considered a file name. + std::string output_; + + /// @brief Maximum number of log files in rotation + int maxver_; + + /// @brief Maximum log file size + uint64_t maxsize_; +}; + +/// @brief structure that describes one logging entry +/// +/// This is a structure that conveys one logger entry configuration. +/// The structure in JSON form has the following syntax: +/// { +/// "name": "*", +/// "output_options": [ +/// { +/// "output": "/path/to/the/logfile.log", +/// "maxver": 8, +/// "maxsize": 204800 +/// } +/// ], +/// "severity": "WARN", +/// "debuglevel": 99 +/// }, +struct LoggingInfo { + + /// @brief logging name + std::string name_; + + /// @brief describes logging severity + isc::log::Severity severity_; + + /// @brief debuglevel (used when severity_ == DEBUG) + /// + /// We use range 0(least verbose)..99(most verbose) + int debuglevel_; + + /// @brief specific logging destinations + std::vector destinations_; +}; + +/// @brief storage for logging information in log4cplus format +typedef std::vector LoggingInfoStorage; + +/// @brief Specifies current DHCP configuration +/// +/// @todo Migrate all other configuration parameters from cfgmgr.h here +struct Configuration { + + /// @brief logging specific information + LoggingInfoStorage logging_info_; +}; + +/// @brief pointer to the configuration +typedef boost::shared_ptr ConfigurationPtr; + +} // namespace isc::dhcp +} // namespace isc + +#endif diff --git a/src/lib/dhcpsrv/daemon.cc b/src/lib/dhcpsrv/daemon.cc index ccf02344de..58434d2ac7 100644 --- a/src/lib/dhcpsrv/daemon.cc +++ b/src/lib/dhcpsrv/daemon.cc @@ -15,11 +15,13 @@ #include #include #include +#include #include +#include #include /// @brief provides default implementation for basic daemon operations -/// +/// /// This file provides stub implementations that are expected to be redefined /// in derived classes (e.g. ControlledDhcpv6Srv) namespace isc { @@ -53,6 +55,41 @@ void Daemon::handleSignal() { } } +void Daemon::configureLogger(isc::data::ConstElementPtr log_config, + const ConfigurationPtr& storage) { + + // This is utility class that translates JSON structures into formats + // understandable by log4cplus. + LogConfigParser parser(storage); + + if (!log_config) { + // There was no logger configuration. Let's clear any config + // and revert to the default. + + parser.defaultLogging(); // Set up default logging + return; + } + + isc::data::ConstElementPtr loggers; + loggers = log_config->get("loggers"); + if (!loggers) { + // There is Logging structure, but it doesn't have loggers + // array in it. Let's clear any old logging configuration + // we may have and revert to the default. + + parser.defaultLogging(); // Set up default logging + return; + } + + // Translate JSON structures into log4cplus formats + parser.parseConfiguration(loggers); + + // Apply the configuration + + /// @todo: Once configuration unrolling is implemented, + /// this call will be moved to a separate method. + parser.applyConfiguration(); +} }; }; diff --git a/src/lib/dhcpsrv/daemon.h b/src/lib/dhcpsrv/daemon.h index b670c22423..c090c443bc 100644 --- a/src/lib/dhcpsrv/daemon.h +++ b/src/lib/dhcpsrv/daemon.h @@ -12,12 +12,16 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +#ifndef DAEMON_H +#define DAEMON_H + #include +#include +#include #include #include #include - namespace isc { namespace dhcp { @@ -105,7 +109,7 @@ public: return (config_file_); } - /// Initializes logger + /// @brief Initializes logger /// /// This method initializes logger. Currently its implementation is specific /// to each configuration backend. @@ -114,6 +118,19 @@ public: /// @param verbose verbose mode (true usually enables DEBUG messages) static void loggerInit(const char* log_name, bool verbose); + /// @brief Configures logger + /// + /// Applies configuration stored in "Logging" structure in the + /// configuration file. This structure has a "loggers" array that + /// contains 0 or more entries, each configuring one logging source + /// (name, severity, debuglevel), each with zero or more outputs (file, + /// maxsize, maximum number of files). + /// + /// @param log_config JSON structures that describe logging + /// @param storage configuration will be stored here + static void configureLogger(isc::data::ConstElementPtr log_config, + const isc::dhcp::ConfigurationPtr& storage); + protected: /// @brief Invokes handler for the next received signal. @@ -150,3 +167,5 @@ private: }; // end of isc::dhcp namespace }; // end of isc namespace + +#endif diff --git a/src/lib/dhcpsrv/logging.cc b/src/lib/dhcpsrv/logging.cc new file mode 100644 index 0000000000..69648c5838 --- /dev/null +++ b/src/lib/dhcpsrv/logging.cc @@ -0,0 +1,220 @@ +// Copyright (C) 2014 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 +#include +#include +#include +#include +#include + +using namespace isc::data; +using namespace isc::log; + +namespace isc { +namespace dhcp { + +static const char* DEFAULT_SYSLOG_NAME = "kea"; + +LogConfigParser::LogConfigParser(const ConfigurationPtr& storage) + :config_(storage) { + if (!storage) { + isc_throw(InvalidOperation, "LogConfigParser needs a pointer to the " + "configuration, so parsed data can be stored there"); + } +} + +void LogConfigParser::parseConfiguration(isc::data::ConstElementPtr loggers) { + + // Iterate over all entries in "Logging/loggers" list + BOOST_FOREACH(ConstElementPtr logger, loggers->listValue()) { + parseConfigEntry(logger); + } +} + +void LogConfigParser::parseConfigEntry(isc::data::ConstElementPtr entry) { + if (!entry) { + // This should not happen, but let's be on the safe side and check + return; + } + + if (!config_) { + isc_throw(BadValue, "configuration storage not set, can't parse logger config."); + } + + LoggingInfo info; + + // Get a name + isc::data::ConstElementPtr name_ptr = entry->get("name"); + if (!name_ptr) { + isc_throw(BadValue, "loggers entry does not have a mandatory 'name' " + "element (" << entry->getPosition() << ")"); + } + info.name_ = name_ptr->stringValue(); + + // Get severity + isc::data::ConstElementPtr severity_ptr = entry->get("severity"); + if (!name_ptr) { + isc_throw(BadValue, "loggers entry does not have a mandatory " + "'severity' element (" << entry->getPosition() << ")"); + } + try { + info.severity_ = isc::log::getSeverity(severity_ptr->stringValue().c_str()); + } catch (const std::exception& ex) { + isc_throw(BadValue, "Unable to convert '" << severity_ptr->stringValue() + << "' into allowed severity (" << severity_ptr->getPosition() + << ")"); + } + + // Get debug logging level + info.debuglevel_ = 0; + isc::data::ConstElementPtr debuglevel_ptr = entry->get("debuglevel"); + + // It's ok to not have debuglevel, we'll just assume its least verbose + // (0) level. + if (debuglevel_ptr) { + try { + info.debuglevel_ = boost::lexical_cast(debuglevel_ptr->str()); + if ( (info.debuglevel_ < 0) || (info.debuglevel_ > 99) ) { + // Comment doesn't matter, it is caught several lines below + isc_throw(BadValue, ""); + } + } catch (...) { + isc_throw(BadValue, "Unable to convert '" << debuglevel_ptr->stringValue() + << "' into allowed debuglevel range (0-99) (" + << debuglevel_ptr->getPosition() << ")"); + } + } + + isc::data::ConstElementPtr output_options = entry->get("output_options"); + + if (output_options) { + parseOutputOptions(info.destinations_, output_options); + } + + config_->logging_info_.push_back(info); +} + +void LogConfigParser::parseOutputOptions(std::vector& destination, + isc::data::ConstElementPtr output_options) { + if (!output_options) { + isc_throw(BadValue, "Missing 'output_options' structure in 'loggers'"); + } + BOOST_FOREACH(ConstElementPtr output_option, output_options->listValue()) { + + LoggingDestination dest; + + isc::data::ConstElementPtr output = output_option->get("output"); + if (!output) { + isc_throw(BadValue, "output_options entry does not have a mandatory 'output' " + "element (" << output_option->getPosition() << ")"); + } + dest.output_ = output->stringValue(); + + isc::data::ConstElementPtr maxver_ptr = output_option->get("maxver"); + if (maxver_ptr) { + dest.maxver_ = boost::lexical_cast(maxver_ptr->str()); + } + + isc::data::ConstElementPtr maxsize_ptr = output_option->get("maxsize"); + if (maxsize_ptr) { + dest.maxsize_ = boost::lexical_cast(maxsize_ptr->str()); + } + + destination.push_back(dest); + } +} + +void LogConfigParser::applyConfiguration() { + + // Constants: not declared static as this is function is expected to be + // called once only + static const std::string STDOUT = "stdout"; + static const std::string STDERR = "stderr"; + static const std::string SYSLOG = "syslog"; + static const std::string SYSLOG_COLON = "syslog:"; + + // Set locking directory to /tmp + setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1); + + // Now iterate through all specified loggers + for (LoggingInfoStorage::const_iterator it = config_->logging_info_.begin(); + it != config_->logging_info_.end(); ++it) { + + // Prepare the objects to define the logging specification + LoggerSpecification spec(it->name_, + it->severity_, + it->debuglevel_); + OutputOption option; + + for (std::vector::const_iterator dest = it->destinations_.begin(); + dest != it->destinations_.end(); ++dest) { + + // Set up output option according to destination specification + if (dest->output_ == STDOUT) { + option.destination = OutputOption::DEST_CONSOLE; + option.stream = OutputOption::STR_STDOUT; + + } else if (dest->output_ == STDERR) { + option.destination = OutputOption::DEST_CONSOLE; + option.stream = OutputOption::STR_STDERR; + + } else if (dest->output_ == SYSLOG) { + option.destination = OutputOption::DEST_SYSLOG; + // Use default specified in OutputOption constructor for the + // syslog destination + + } else if (dest->output_.find(SYSLOG_COLON) == 0) { + option.destination = OutputOption::DEST_SYSLOG; + // Must take account of the string actually being "syslog:" + if (dest->output_ == SYSLOG_COLON) { + // The expected syntax is syslog:facility. User skipped + // the logging name, so we'll just use DEFAULT_SYSLOG_NAME. + option.facility = DEFAULT_SYSLOG_NAME; + + } else { + // Everything else in the string is the facility name + option.facility = dest->output_.substr(SYSLOG_COLON.size()); + } + + } else { + // Not a recognised destination, assume a file. + option.destination = OutputOption::DEST_FILE; + option.filename = dest->output_; + } + + // ... and set the destination + spec.addOutputOption(option); + } + + LoggerManager manager; + manager.process(spec); + } +} + +void LogConfigParser::defaultLogging() { + LoggerSpecification spec("kea", isc::log::INFO, 0); + + OutputOption option; + option.destination = OutputOption::DEST_CONSOLE; + option.stream = OutputOption::STR_STDOUT; + + spec.addOutputOption(option); + + LoggerManager manager; + manager.process(spec); +} + +} // namespace isc::dhcp +} // namespace isc diff --git a/src/lib/dhcpsrv/logging.h b/src/lib/dhcpsrv/logging.h new file mode 100644 index 0000000000..37fd70001e --- /dev/null +++ b/src/lib/dhcpsrv/logging.h @@ -0,0 +1,92 @@ +// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef DHCPSRV_LOGGING_H +#define DHCPSRV_LOGGING_H + +#include +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Interprets JSON structures and translates them to log4cplus +/// +/// This parser iterates over provided JSON structures and translates them +/// into values applicable to log4cplus. +/// +/// This class uses Configuration structure to store logging configuration. +class LogConfigParser { +public: + + /// @brief Constructor + /// + /// @param storage parsed logging configuration will be stored here + LogConfigParser(const ConfigurationPtr& storage); + + /// @brief Parses specified configuration + /// + /// Walks over specified logging configuration JSON structures and store + /// parsed information in config_->logging_info_. + /// + /// @param log_config JSON structures to be parsed + void parseConfiguration(isc::data::ConstElementPtr log_config); + + /// @brief Applies stored configuration + void applyConfiguration(); + + /// @brief Configures default logging + static void defaultLogging(); + +protected: + + /// @brief Parses one JSON structure in Logging/loggers" array + /// + /// The structure has the following syntax: + /// { + /// "name": "*", + /// "output_options": [ + /// { + /// "output": "/home/thomson/kea-inst/kea-warn.log", + /// "maxver": 8, + /// "maxsize": 204800 + /// } + /// ], + /// "severity": "WARN" + /// } + /// + /// @param entry JSON structure to be parsed + /// @brief parses one structure in Logging/loggers. + void parseConfigEntry(isc::data::ConstElementPtr entry); + + /// @brief Parses output_options structure + /// + /// @param destination parsed parameters will be stored here + /// @param output_options element to be parsed + void parseOutputOptions(std::vector& destination, + isc::data::ConstElementPtr output_options); + + /// @brief Configuration is stored here + /// + /// LogConfigParser class uses only config_->logging_info_ field. + ConfigurationPtr config_; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // CFGMGR_H