// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. // Note: the prefix "log" to all labels is an abbreviation for "Logging" // and is used to prevent a clash with symbols in any other Doxygen file. /** @page logKeaLogging Kea Logging @section logBasicIdeas Basic Ideas The Kea logging system is based on the log4J logging system common in Java development, and includes the following ideas: - A set of severity levels. - A hierarchy of logging sources. - Separation of message use from message text. @subsection logSeverity Message Severity Each message logged by Kea has a severity associated with it, ranging from FATAL - the most severe - to DEBUG - messages output as the code executes to facilitate debugging. In order of decreasing severity, the levels are:
FATAL
The program has encountered an error that is so severe that it cannot continue (or there is no point in continuing). For example, an unhandled exception generated deep within the code has been caught by the top-level program. When a fatal error has been logged, the program will exit immediately (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
(Warning) An unusual event happened. Although the program will continue working normally, the event was sufficiently out of the ordinary to warrant drawing attention to it. For example, the authoritative server loaded a zone that contained no resource records.
INFO
(Information) 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
Debug messages are output at this severity. Each message also has a debug level associated with it, ranging from 0 (the default) for high-level messages and level 99 (the maximum) for the the lowest level.
When logging is enabled for a component, it is enabled for a particular severity level and all higher severities. So if logging is enabled for INFO messages, WARN, ERROR and FATAL messages will also be logged, but not DEBUG ones. If enabled for ERROR, only ERROR and FATAL messages will be logged. As noted above, DEBUG messages are also associated with a debug level. This allows the developer to control the amount of debugging information produced; the higher the debug level, the more information is output. For example, if debugging the NSAS (nameserver address store), debug levels might be assigned as follows: a level 0 debug message records the creation of a new zone, a level 10 logs every timeout when trying to get a nameserver address, a level of 50 records every query for an address and a level of 70 records every update of the round-trip time. Like severities, levels are cumulative; so if level 25 is set as the debug level, all debug messages associated levels 0 to 25 (inclusive) will be output. In fact, it is probably easier to visualize the debug levels as part of the severity system: @code FATAL Most severe ERROR WARN INFO DEBUG level 0 DEBUG level 1 : DEBUG level 99 Least severe @endcode When a particular debug level is set, it - and all debug levels and severities above it - will be logged. To try to ensure that the information from different modules is roughly comparable for the same debug level, a set of standard debug levels has been defined for common types of debug output. (These can be found in @ref log_dbglevels.h) However, modules are free to set their own debug levels or define additional ones. @subsection logHierarchical Hierarchical Logging System When a program writes a message to the logging system, it does so using an instance of the @ref isc::log::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 logger associated with the resolver's cache code could write debugging and other messages to a file while all other components only write messages relating to errors 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; this does not have a parent. The reason for this hierarchy is that unless a logger explicitly assigns a value to an attribute (such as severity of messages it should log), it picks it up the value from the parent. In Kea, each component (kea-dhcp4, kea-dhcp-ddns etc.) has a root logger (named after the program) and every other logger in the component is a child of that. So in the example above, the error/syslog attributes could be associated with the kea-resolver logger while the logger associated with the cache sets its own values for the debug/file attributes. More information about the logging hierarchy can be found in the section on Logging configuration in the Kea Administrator Reference Manual. @subsection logSeparationUseText Separation of Messages Use from Message Text Separating the use of the message from the text associated with it - in essence, defining message text in an external file - allows for the replacement the supplied text of the messages with a local language version. It also means that other attributes can be associated with the message, for example, an explanation of the meaning of the message and other information such as remedial action in the case of errors. Each message has an identifier such as "LOG_WRITE_ERROR". Within the program, this is the symbol passed to the logging system which uses the symbol as an index into a dictionary to retrieve the message associated with it (e.g. "unable to open %1 for input"), after which it substitutes any message parameters (in this example, the name of the file where the write operation failed) and logs the result to the destination. In Kea, a the default text for each message is linked into the program. Each program is able to read a locally-defined message file when it starts, updating the stored definitions with site-specific text. When the message is logged, the updated text is output. However, the message identifier is always included in the output so that the origin of the message can be identified even if the text has been changed. @note Local message files have not yet been implemented in Kea. @section logDeveloperUse Using Logging in a Kea Component The steps in using the logging system in a Kea component (such as an executable or library) are:
  1. Create a message file. This defines messages by an identification string and and text that explains the message in more detail. Ideally the file should have a file type of ".mes".
  2. Run it through the message compiler to produce the files for your module. This step should be included in the build process. The message compiler is a Kea program and is one of the first programs built and linked in the build process. As a result, it should be available for compiling the message files of all Kea components and libraries. For C++ development, the message compiler produces two files in the default directory, having the same name as the input file but with file types of ".h" and ".cc".
  3. Include the resultant files in your source code to define message symbols, and compile the code and link it into your program.
  4. Declare loggers in your code and use them to log messages.
  5. Call the logger initialization function in the program's main module.
The following sections describe these steps in more detail. @subsection logMessageFiles Create a Message File A message file contains message definitions. Typically there will be one message file for each component that uses Kea logging. An example file could be: @code # Example message file $NAMESPACE isc::log % LOG_UNRECOGNIZED_DIRECTIVE line %1: unrecognized directive '%2' A line starting with a dollar symbol was found, but the first word on the line (shown in the message) was not a recognized message compiler directive. % LOG_WRITE_ERROR error writing to %1: %2 The specified error was encountered by the message compiler when writing to the named output file. @endcode Points to note are: Although there are few restriction on what can be in the message identification and text, there are a number of conventions used by Kea, both in the contents of the message and in the usage. All code should adhere to these: @subsection logSourceFiles Produce Source Files The message file created in the previous step is then run through the message compiler to produce source files that are included in the Kea programs. @subsubsection logMessageCompiler Message Compiler The message compiler is a program built in the src/log/compiler directory. It is invoked by the command: @code kea-msg-compiler [-h] [-v] [-d dir] @endcode "-v" prints the version number and exits; "-h" prints brief help text. Finally, the "-d" switch directs the compiler to produce the output files in the specified directory (the default being the current working directory). C++ Files
  1. A C++ header file (called .h) holding lines of the form: @code namespace { extern const isc::log::MessageID LOG_BAD_DESTINATION; extern const isc::log::MessageID LOG_BAD_SEVERITY; : } @endcode The symbols define keys in the global message dictionary, with the namespace enclosing the symbols set by the $NAMESPACE directive. (This is the reason for the restriction on message identifiers - they have to be valid C++ symbol names.)
  2. A C++ source file (called .cc) that holds the definitions of the global symbols and code to insert the symbols and messages into an internal dictionary. Symbols are defined to be equal to strings equal to the identifier, e.g. @code extern const isc::log::MessageID LOG_BAD_DESTINATION = "LOG_BAD_DESTINATION"; extern const isc::log::MessageID LOG_BAD_SEVERITY = "LOG_BAD_SEVERITY"; : @endcode (The current implementation allows symbols to be compared. However, use of strings should not be assumed - a future implementation may change this.) In addition, the file declares an array of identifiers/messages and an object to add them to the global dictionary, e.g.: @code namespace { const char* values[] = { "LOG_BAD_DESTINATION", "unrecognized log destination: %1", "LOG_BAD_SEVERITY", "unrecognized log severity: %1", : NULL }; const isc::log::MessageInitializer initializer(values); } @endcode The constructor of the @ref isc::log::MessageInitializer object retrieves the singleton global @ref isc::log::MessageDictionary object (created using standard methods to avoid the "static initialization fiasco") and adds each identifier and associated text to it. These constructors are run when the program starts; a check is made as each identifier is added and, if the identifier already exists in the dictionary, a warning message is printed to the main logging output when logging is finally enabled. The appearance of such a message indicates a programming error.
@subsubsection logMakefile Include Message Compilation in Makefile The source file for the messages is the ".mes" file, but the files used by the code which must be compiled and linked are the output of the message compiler. (The compiler is produced very early on in the Kea build sequence, so is available for use in the building of subsequent components.) To allow this, certain dependencies must be included in the Makefile.am for each component that uses logging. Including Message files in C++ Component Builds
The following segment from the "hooks" Makefile.am illustrates the entries needed. @code # Define rule to build logging source files from message file hooks_messages.h hooks_messages.cc: s-messages s-messages: hooks_messages.mes $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/hooks/hooks_messages.mes touch $@ # Tell automake that the message files are built as part of the build process # (so that they are built before the main library is built). BUILT_SOURCES = hooks_messages.h hooks_messages.cc # Ensure that the message file is included in the distribution EXTRA_DIST = hooks_messages.mes # Get rid of generated message files on a clean CLEANFILES = *.gcno *.gcda hooks_messages.h hooks_messages.cc s-messages @endcode The first two rules relate the output .h and .cc files produced by the message compiler to the input .mes file. The intermediate "s-messages" file is used to overcome synchronization issues with parallel builds (where "make" uses multiple processes running in parallel). Note that the reference to both the compiler and the input message file are via absolute paths defined in terms of Automake macros. In particular it is important that the message compiler - which is created during the build process - is referred to via the "top_builddir" macro, whereas the input message file - which is in the repository - is accessed through the "top_srcdir" macro. The BUILT_SOURCES line notifies the Automake that the .h and .cc files need to be created before they can be used in the compilation, so instructs it to organize things so that the message compiler is run first. As the .mes file is not directly included in any compilation, it will not be automatically copied into a distribution created through this Makefile.am. The EXTRA_DIST line informs Automake that this file does need to be included. Finally, the intermediate files - the .cc and .h file, as well as the intermediate s-messages file - need to be removed when "make clean" is run. These files are therefore included in the definition of the CLEANFILES macro. Not shown are the Makefile.am lines where the .h and .cc file are used. These are the same as other lines specifying .h and .cc source files. @subsection logUsage Using Logging Files in Program Development @subsubsection logCppUsage Use in a C++ Program or Module To use logging in a C++ program or module:
  1. Build the message header file and source file as described above.
  2. In each C++ file in which logging is to be used, declare a logger through which the message will be logged. @code isc::log::Logger logger("name"); @endcode This declaration can be per-function, or it can be declared statically in file scope. The string passed to the constructor is the name of the logger (it can be any string) and is used when configuring it. (Remember though that the name of root logger for the program will be prepended to the name chosen. So if, for example, the name "cache" is chosen and the model is included in the "kea-resolver" program, the full name of the logger will be "kea-resolver.cache".) Loggers with the same name share the same configuration. For this reason if, as is usual, messages logged in different files in the same component (e.g. hooks module, nameserver address store, etc.) originate from loggers with the same name, the logger declaration can be placed into a header file.
  3. Issue logging calls using supplied macros in "log/macros.h", e.g. @code LOG_ERROR(logger, LOG_WRITE_ERROR).arg("output.txt"); LOG_DEBUG(nsas_logger, NSAS_DBG_TRACE, NSAS_LOOKUP_CANCEL).arg(zone); @endcode All macros (with the exception of LOG_DEBUG) take two arguments: the C++ logger object that will be used to log the message, and the identification of the message to be logged. LOG_DEBUG takes three arguments, the additional one being the debug level associated with the message. The .arg() call appended to the end of the LOG_XXX() macro handles the arguments to the message. A chain of these is used in cases where a message takes multiple arguments, e.g. @code LOG_DEBUG(nsas_logger, NSAS_DBG_RTT, NSAS_UPDATE_RTT) .arg(addresses_[family][index].getAddress().toText()) .arg(old_rtt).arg(new_rtt); @endcode Using the macros is more efficient than direct calls to the methods on the logger class: they avoid the overhead of evaluating the parameters to arg() if the logging settings are such that the message is not going to be output (e.g. it is a DEBUG message and the logging is set to output messages of INFO severity or above).
  4. The main program unit must include a call to isc::log::initLogger() (described in more detail below) to set the initial logging severity, debug log level, and external message file.
@subsection logInitialization Logging Initialization In all cases, if an attempt is made to use a logging method before the logging has been initialized, the program will terminate with a LoggingNotInitialized exception. @subsection logInitializationCpp C++ Initialization Logging Initialization is carried out by calling @ref isc::log::initLogger(). There are two variants to the call, one for use by production programs and one for use by unit tests. @subsubsection logInitializationCppVariant1 Variant #1, Used by Production Programs The call that should be used by all production programs is: @code void isc::log::initLogger(const std::string& root, isc::log::Severity severity = isc::log::INFO, int dbglevel = 0, const char* file = NULL, bool buffer = false); @endcode Arguments are:
root
Name of the root logger. This should be the name of the program (e.g. "kea-auth") and is used when configuring logging.
severity
Default severity that the program will start logging with. Although this may be overridden when the program obtains its configuration from the configuration database, this is the severity that it used until then. The logging severity is one of the enum defined in @ref logger.h, i.e. @code isc::log::DEBUG isc::log::INFO isc::log::WARN isc::log::ERROR isc::log::FATAL isc::log::NONE @endcode (The level NONE may be used to disable logging.)
dbglevel
The debug log level is only interpreted when the severity is isc::log::DEBUG and is an integer ranging 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.
file
The name of a local message file. This will be read and its definitions used to replace the compiled-in text of the messages. The default value of NULL indicates that no local message file is supplied.
buffer
If set to true, initial log messages will be internally buffered, until the first time a logger specification is processed. This way the program can use logging before even processing its logging configuration. As soon as any specification is processed (even an empty one), the buffered log messages will be flushed according to the specification. Note that if this option is used, the program SHOULD call one of the @ref isc::log::LoggerManager::process() calls. If the program exits before this is done, all log messages are dumped in a raw format to stdout (so that no messages get lost).
@subsubsection logInitializationCppVariant2 Variant #2, Used by Unit Tests @code void isc::log::initLogger() @endcode This is the call that should be used by unit tests. In this variant, all the options are supplied by environment variables: it should not be used for production programs to avoid the chance that the program operation is affected by inadvertently-defined environment variables. The environment variables are:
KEA_LOGGER_ROOT
Sets the "root" for the unit test. If not defined, the name "kea" is used.
KEA_LOGGER_SEVERITY
The severity to set for the root logger in the unit test. Valid values are "DEBUG", "INFO", "WARN", "ERROR", "FATAL" and "NONE". If not defined, "INFO" is used.
KEA_LOGGER_DBGLEVEL
If KEA_LOGGER_SEVERITY is set to "DEBUG", the debug level. This can be a number between 0 and 99, and defaults to 0.
KEA_LOGGER_LOCALMSG
If defined, points to a local message file. The default is not to use a local message file.
KEA_LOGGER_DESTINATION
The location to which log message are written. This can be one of:
  • stdout Message are written to stdout.
  • stderr Messages are written to stderr.
  • syslog[:facility] Messages are written to syslog. If the optional "facility" is used, the messages are written using that facility. (This defaults to "local0" if not specified.)
  • Anything else Interpreted as the name of a file to which output is appended. If the file does not exist, a new one is opened.
In the case of "stdout", "stderr" and "syslog", they must be written exactly as is - no leading or trailing spaces, and in lower-case.
@subsection logInitializationHooks Hooks Specific Notes All hooks libraries should use Kea logging mechanisms. The loggers and the library specific log messages are created in the same way as for the core Kea modules. The loggers created within the hook library belong to the logging hierarchy of the Kea process and their configuration can be controlled from the Kea configuration file. If the configuration file doesn't contain the specific configuration for the logger used in the library, this logger is given the configuration of the root Kea logger. The hook libraries are loaded dynamically. This requires that the global log messages dictionary, holding the mapping of specific log message identifiers to the actual messages, is updated to include the messages specified in the hook library when the library is loaded. Conversely, the messages have to be removed from the dictionary when the library is unloaded. The new messages are added to the global dictionary using the @ref isc::log::MessageInitializer::loadDictionary static function. It is called by the @ref isc::hooks::LibraryManager::loadLibrary for each loaded library. When the library is unloaded, the instance of the @ref isc::log::MessageInitializer defined in the library is destroyed and its destructor removes the messages registered by the destroyed instance from the global dictionary. The hook library itself must not perform any action to register or unregister log messages in the global dictionary! @section logNotes Notes on the Use of Logging One thing that should always be kept in mind is whether the logging could be used as a means for a DOS attack. For example, if a warning message is logged every time an invalid packet is received, an attacker could simply send large numbers of invalid packets. Of course, warnings could be disabled (or just warnings for that that particular logger), but nevertheless the message is an attack vector. As a general rule, if the message can be triggered by a user action, it can be used as an attack vector. There are two approaches to get round this:
  1. Log messages generated by such user actions as DEBUG messages. DEBUG is not enabled by default, so these events will not be recorded unless DEBUG is specifically enabled. Choosing a suitable debug level for such messages will select only those messages and not the more general debug messages.
  2. Record system-related and packet-related messages via different loggers. As the loggers are independent and the severity levels independent, fine-tuning of what and what is not recorded can be achieved.
*/