2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 21:45:37 +00:00

[master] Merge branch 'trac2566'

This commit is contained in:
Stephen Morris
2014-01-27 13:07:10 +00:00
4 changed files with 767 additions and 534 deletions

View File

@@ -86,10 +86,10 @@
* - @subpage libdhcp_ddns
*
* @section miscellaneousTopics Miscellaneous Topics
* - @subpage LoggingApi
* - @subpage LoggingApiOverview
* - @subpage LoggingApiLoggerNames
* - @subpage LoggingApiLoggingMessages
* - @subpage logBind10Logging
* - @subpage logBasicIdeas
* - @subpage logDeveloperUse
* - @subpage logNotes
* - @subpage SocketSessionUtility
* - <a href="./doxygen-error.log">Documentation warnings and errors</a>
*

View File

@@ -32,7 +32,7 @@ libb10_log_la_SOURCES += message_types.h
libb10_log_la_SOURCES += output_option.cc output_option.h
libb10_log_la_SOURCES += buffer_appender_impl.cc buffer_appender_impl.h
EXTRA_DIST = README
EXTRA_DIST = logging.dox
EXTRA_DIST += logimpl_messages.mes
EXTRA_DIST += log_messages.mes

View File

@@ -1,529 +0,0 @@
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 use from message 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 Use from Message Text
============================================
By separating the use of the message from the text associated with this -
in essence, defining message text in an external file - it is possible to
replace the supplied text of the messages with a local language version.
Each message is identified by an identifier e.g. "LOG_WRITE_ERROR".
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"). It then substitutes any message parameters (in this example,
the name of the file where the write operation failed) and logs it to
the destination.
In BIND-10, 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.
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, the convention being that these are a few
words separated by underscores - and text that explains the message in
more detail. The file is described in more detail below.
Ideally the file should have a file type of ".mes".
2. Run it through the message compiler to produce the .h and .cc files. This
step should be included in the build process. (For an example of how to
do this, see src/lib/nsas/Makefile.am.) During development though, the
message compiler (found in the "src/lib/log/compiler" directory) will need
to be run manually. The only argument is the name of the message file: it
will produce as output 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 .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 initLogger (declared
in logger_support.h) in the main program unit.
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
$NAMESPACE isc::log
% LOG_UNRECOGNISED_DIRECTIVE line %1: unrecognised 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 recognised 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.
-- END --
Points to note:
* Leading and trailing space are trimmed from the line. Although the above
example 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, just one directive is
recognised:
* $NAMESPACE, which has one argument: the namespace in which the symbols are
created. In the absence of a $NAMESPACE directive, symbols will be put in
the anonymous namespace.
* Message lines. These start with a "%" and are followed by the message
identification and the message text, the latter including zero or more
replacement tokens, e.g.
% LOG_WRITE_ERROR error writing to %1: %2
* There may be zero or more spaces between the leading "%" and the message
identification (which, in the example above, is the string
"LOG_WRITE_ERROR").
* The message identification can be any string of letters, digits and
underscores, but should not start with a digit. The convention adopted
in BIND 10 is for the first component (before the first underscore) to be
a string indicating the origin of the message, and the remainder to
describe the message. So in the example above, the LOG_ indicates that
the error originated from the logging library and the "WRITE_ERROR"
indicates that there was a problem in a write operation.
* The rest of the line - from the first non-space character to the
last non- space character - is taken exactly for the text
of the message. There are no restrictions on what characters may
be in this text, other than they be printable. (This means that
both single-quote (') and double-quote (") characters are allowed.)
The message text may include replacement tokens (the strings "%1",
"%2" etc.). When a message is logged, these are replaced with the
arguments passed to the logging call: %1 refers to the first argument,
%2 to the second etc. Within the message text, the placeholders
can appear in any order and placeholders can be repeated. Otherwise,
the message is printed unmodified.
* Remaining lines 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. They are ignored by the message compiler.
Message Compiler
----------------
The message compiler is a program built in the src/log/compiler directory.
It is invoked by the command:
message [-h] [-v] -p] <message-file>
("-v" prints the version number and exits; "-h" prints brief help text.) The
compiler produces source files for C++ and Python.
C++ Files
---------
Without the "-p" option, the message compiler 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 <namespace> {
extern const isc::log::MessageID LOG_WRITE_ERROR;
:
}
The symbols define the 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 <message-file-name>.cc) that holds the definitions
of the global symbols and code to insert the symbols and messages into the map.
Symbols are defined to be equal to strings holding the identifier, e.g.
extern const isc::log::MessageID LOG_WRITE_ERROR = "LOG_WRITE_ERROR";
(The 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:
namespace {
const char* values[] = {
identifier1, text1,
identifier2, text2,
:
NULL
};
const isc::log::MessageInitializer initializer(values);
}
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).
Python Files
------------
If the "-p" option is given, the compiler produces a Python module defining
the messages. The format of this is:
import isc.log
:
LOG_WRITE_ERROR = isc.log.create_message("LOG_WRITE_ERROR",
"error writing to %1 : %2")
(The definition is output on one line - it is split across two lines in this
document for readability.)
The module can be imported into other Python code, and messages logged
in a similar way to C++ using the Python logging library.
Using the Logging - C++
=======================
1. Build message header file and source file as describe above.
2. The main program unit must include a call to isc::log::initLogger()
(described in more detail below) 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::DEBUG
isc::log::INFO
isc::log::WARN
isc::log::ERROR
isc::log::FATAL
isc::log::NONE
b) The debug log level is only interpreted when the severity is
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.
c) The external message file. If present, this is the same as a
standard message file, although it should not include any
directives. (A single directive of a particular type will be
ignored; multiple directives will cause the read of the file to
fail with an error.)
The settings remain in effect until the logging configuration is read,
and so provide the default logging during program initialization.
3. Declare a logger through which the message will be logged.
isc::log::Logger logger("name");
The string passed to the constructor is the name of the logger (it
can be any string) and is used when configuring it. Loggers with
the same name share the same configuration.
4. Issue logging calls using supplied macros in "log/macros.h", e.g.
LOG_ERROR(logger, LOG_WRITE_ERROR).arg("output.txt");
(The macros are more efficient that calls to the methods on the logger
class: they avoid the overhead of evaluating the parameters to arg()
if the settings are such that the message is not going to be output.)
Using the Logging - Python
==========================
1. Build message module as describe above.
2. The main program unit must include a call to isc.log.init()
(described in more detail below) to set the to set the logging
severity, debug log level, and external message file:
a) The logging severity is one of the strings:
DEBUG
INFO
WARN
ERROR
FATAL
NONE
b) The debug log level is only interpreted when the severity is
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.
c) The external message file. If present, this is the same as a
standard message file, although it should not include any
directives. (Any that are there will be ignored.)
The settings remain in effect until the logging configuration is read,
and so provide the default logging during program initialization.
3. Declare a logger through which the message will be logged.
isc.log.Logger logger("name")
The string passed to the constructor is the name of the logger (it
can be any string) and is used when configuring it. Loggers with
the same name share the same configuration.
4. Issue calls to the logging methods:
logger.error(LOG_WRITE_ERROR, "output.txt");
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.
C++
---
Logging Initialization is carried out by calling initLogger(). There are two
variants to the call, one for use by production programs and one for use by
unit tests.
Variant #1, Used by Production Programs
---------------------------------------
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);
This is the call that should be used by production programs:
root
Name of the program (e.g. "b10-auth"). This is also the name of the root
logger 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. (This may be set by
a command-line parameter.)
dbglevel
The debug level used if "severity" is set to isc::log::DEBUG.
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.
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 LoggerManager's process() calls (if you
are using the built-in logging configuration handling in ModuleCCSession,
this is automatically handled). 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).
Variant #2, Used by Unit Tests
------------------------------
void isc::log::initLogger()
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:
B10_LOGGER_ROOT
Sets the "root" for the unit test. If not defined, the name "bind10" is used.
B10_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.
B10_LOGGER_DBGLEVEL
If B10_LOGGER_SEVERITY is set to "DEBUG", the debug level. This can be a
number between 0 and 99, and defaults to 0.
B10_LOGGER_LOCALMSG
If defined, points to a local message file. The default is not to use a local
message file.
B10_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.
Python
------
To be supplied
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.
One thing that should always be borne in mind is whether the logging
could be used as a vector 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.)
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 (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
drawing 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 70 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.
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 type of debug output. However, modules are free
to set their own debug levels or define additional ones.
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 denial of service vector. For
example, suppose that the main authoritative 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, server 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.
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.

762
src/lib/log/logging.dox Normal file
View File

@@ -0,0 +1,762 @@
// Copyright (C) 2013-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.
// 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 logBind10Logging BIND 10 Logging
@section logBasicIdeas Basic Ideas
The BIND 10 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 BIND 10 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:
<dl>
<dt>FATAL</dt>
<dd>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.</dd>
<dt>ERROR</dt>
<dd>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.</dd>
<dt>WARN</dt>
<dd>(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.</dd>
<dt>INFO</dt>
<dd>(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.</dd>
<dt>DEBUG</dt>
<dd>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.</dd>
</dl>
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 visualise 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 BIND 10,
each component (b10-auth, b10-resolver 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 b10-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 <a
href="http://bind10.isc.org/docs/bind10-guide.html#logging">BIND 10
Guide</a>.
@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 BIND 10, 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 BIND 10.
@section logDeveloperUse Using Logging in a BIND 10 Component
The steps in using the logging system in a BIND 10 component (such as
an executable or library) are:
<ol>
<li>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".</li>
<li>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 BIND10 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 BIND 10 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". For Python, the message compiler will produce
a Python module containing the symbols.</li>
<li>Include the resultant files in your source code to define message symbols,
and (for C++) compile the code and link it into your program.</li>
<li>Declare loggers in your code and use them to log messages.</li>
<li>Call the logger initialization function in the program's main module.</li>
</ol>
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 BIND 10 logging.
An example file could be:
@code
# Example message file
$NAMESPACE isc::log
% LOG_UNRECOGNISED_DIRECTIVE line %1: unrecognised 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 recognised 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:
<ul>
<li>Leading and trailing spaces are trimmed from each line before it
is processed. Although the above example has every line starting at
column 1, the lines could be indented if desired.</li>
<li>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 that line.</li>
<li>Lines starting with "$" are directives. At present, just one
directive is recognised:
<dl>
<dt>$NAMESPACE &lt;namespace-name&gt;</dt>
<dd>The sole argument is the name of the namespace in which the
symbols are created. In the absence of a $NAMESPACE directive,
symbols will be put in the anonymous namespace.</dd>
</dl>
</li>
<li>Lines starting with "%" are message definitions and comprise the message
identification and the message text. For example:
@code
% LOG_WRITE_ERROR error writing to %1: %2
@endcode
There may be zero or more spaces between the leading "%" and the
message identification (which, in the example above, is the string
"LOG_WRITE_ERROR").</li>
<li>The message identification can be any string of letters, digits and
underscores, but must not start with a digit.</li>
<li>The rest of the line - from the first non-space character to the
last non- space character - is the text of the message. There are no
restrictions on what characters may be in this text, other than they be
printable (so both single-quote (') and double-quote (") characters are
allowed). The message text may include replacement tokens (the strings
"%1", "%2" etc.). When a message is logged, these are replaced with the
arguments passed to the logging call: %1 refers to the first argument,
%2 to the second etc. Within the message text, the placeholders can
appear in any order and placeholders can be repeated. Otherwise, the
message is printed unmodified.</li>
<li>Remaining lines indicate an explanation for the preceding message.
The explanation can comprise multiple paragraphs, the paragraphs being
separated by blank lines. These lines are intended to be processed by a
separate program to generate an error messages manual; they are ignored
by the message compiler.</li>
<li>Except when used to separate paragraphs in the message explanation,
blank lines are ignored.</li>
</ul>
Although there are few restriction on what can be in the message
identifcation and text, there are a number of conventions used by BIND
10, both in the contents of the message and in the usage. All code
should adhere to these:
<ul>
<li>Message identifications should include at least one underscore.
The component before the first underscore is a string indicating the
origin of the message, and the remainder describes the condition.
So in the example above, the LOG indicates that the error originated
from the logging library and the "WRITE_ERROR" indicates that there was
a problem in a write operation.</li>
<li>The part of the message identification describing the error (e.g.
"WRITE_ERROR" in the example above) should comprise no more than
two or three words separated by underscores. An excessive number
of words or overly long message identification should be avoided;
such information should be put in the text of the message. For example,
"RESOLVER_ERROR_FETCHING_NAME_FROM_UPSTREAM_SERVER" is excessively long,
"RESOLVER_FETCH_ERROR" being better.</li>
<li>Similarly, the text of the message should be reasonably concise. It should
include enough information (possibly supplied at run-time in the form of
parameters) to allow further investigations to be undertaken if required.
Taking the above example, a suitable error message to indicate that the
resolver has failed to read a name from an upstream authoritative server
could be:
@code
% RESOLVER_FETCH_ERROR fetch from %1 failed, error code %2 (%3)
@endcode
... where %1 indicates the name or IP address of the server to which the
fetch was sent, %2 the errno value returned and %3 the message associated
with that error number (retrieved via a call to "strerror()").
</li>
<li>The message should not have a comma after the message identification.
The message text should neither start with a capital letter (unless
the first word is a proper noun or is normally written in capitals)
nor end with a period. The message reporting system takes care of such
punctuation.</li>
<li>The parameters substituted into the message text should not include
line breaks. Messages are normally output to the syslog file which
has the inbuilt assumption of one line per message. Splitting a message
across multiple lines makes it awkward to search the file for messages
and associated information.</li>
<li>The message identifier should be unique across the entire BIND 10
system. (An error will be reported at system start-up if an identifier
is repeated.)</li>
<li>A particular message identifier should only be used at one place in
the BIND 10 code. In this way, if the message indicates a problem, the
code in question can be quickly identified.</li>
<li>The explanation of the message - the free-form text following the
message identification - appears in the BIND 10 message manual. It
should:
<ul>
<li>Describe the serverity of the message (debug, informational etc.)</li>
<li>Expand on the text of the message. In some cases, such as
debug messages, the message text may provide more or less sufficient
description. For warnings and errors, the explanation should provide
sufficient background to the problem to allow a non-developer to
understand the issue and to begin fault-finding. If possible, the
explanation should also include suggested remedial action.</li>
</ul>
</ul>
@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 BIND
10 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
message [-h] [-v] [-p] [-d dir] <message-file>
@endcode
"-v" prints the version number and exits; "-h" prints brief help text.
The compiler produces source files for C++ unless the "-p" switch is
specified, in which case it produces Python code. Finally, the "-d"
switch directs the compiler to produce the output files in the specified
directory (the default being the current working directory).
<b>C++ Files</b><br/>
Without the "-p" option, the message compiler processes the message file
to produce two files:
<ol>
<li>A C++ header file (called <message-file-name>.h) holding lines of
the form:
@code
namespace <namespace-name> {
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.)</li>
<li>A C++ source file (called <message-file-name>.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.
</li>
</ol>
<b>Python Files</b><br/>
If the "-p" option is given, the compiler produces a Python module defining
the messages. The content of this is of the form:
@code
import isc.log
:
LOG_WRITE_ERROR = isc.log.create_message("LOG_WRITE_ERROR",
"error writing to %1 : %2")
@endcode
(The definition is output on one line - it is split across two lines in this
document for readability.)
The module can be imported into other Python code, and messages logged
in a similar way to C++ using the Python logging library.
@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, in the case of C++, must be compiled and linked)
are the output of the message compiler. (The compiler is produced very
early on in the BIND 10 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.
<b>Including Message files in C++ Component Builds</b><br/>
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/message $(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 organse 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.
<b>Including Message files in Python Component Builds</b><br/>
The following (modified) segments from the "xfrin" Makefile.am illustrates
the entries needed.
@code
CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/xfrin_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/xfrin_messages.pyc
:
EXTRA_DIST += xfrin_messages.mes
:
# Define rule to build logging source files from message file
$(PYTHON_LOGMSGPKG_DIR)/work/xfrin_messages.py : xfrin_messages.mes
$(top_builddir)/src/lib/log/compiler/message \
-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/xfrin_messages.mes
@endcode
The CLEANFILES lines remove the created Python (and compiled Python)
code when "make clean" is run.
The EXTRA_DIST line ensures that the .mes file is copied to the
distribution tarball.
The final dependency shows the use of the message compiler to
create the Python message module in a working directory.
@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:
<ol>
<li>Build the message header file and source file as described above.</li>
<li>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 "b10-resolver" program, the
full name of the logger will be "b10-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.</li>
<li>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).</li>
<li>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.
</ol>
@subsubsection logPythonUsage Use in a Python Module
To use logging in a Python module:
<ol>
<li>Build message module as described above.</li>
<li>Declare a logger through which the message will be logged.
@code
isc.log.Logger logger("name")
@endcode
The string passed to the constructor is the name of the logger (it can
be any string) and is used when configuring it. Loggers with the same
name share the same configuration.</li>
<li>Issue calls to the logging methods:
@code
logger.error(LOG_WRITE_ERROR, "output.txt")
@endcode
The message parameters are included as trailing arguments in the
logger call.</li>
<li>The main program unit must include a call to isc.log.init() (described
in more detail below) to set the to set the logging severity, debug log
level, and external message file. The settings remain in effect until
the logging configuration is read, so are the ones used during program
initialization.</li>
</ol>
@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:
<dl>
<dt><code>root</code></dt>
<dd>Name of the root logger. This should be the name of the program
(e.g. "b10-auth") and is used when configuring logging.</dd>
<dt><code>severity</code></dt>
<dd>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.)
</dd>
<dt><code>dbglevel</code></dt>
<dd>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.</dd>
<dt><code>file</code></dt>
<dd>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.</dd>
<dt><code>buffer</code></dt>
<dd>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 you are using the built-in logging configuration handling in @ref
isc::config::ModuleCCSession, this is automatically handled.) 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).</dd>
</dl>
@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:
<dl>
<dt>B10_LOGGER_ROOT</dt>
<dd>Sets the "root" for the unit test. If not defined, the name "bind10"
is used.</dd>
<dt>B10_LOGGER_SEVERITY</dt>
<dd>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.</dd>
<dt>B10_LOGGER_DBGLEVEL</dt>
<dd>If B10_LOGGER_SEVERITY is set to "DEBUG", the debug level. This can
be a number between 0 and 99, and defaults to 0.</dd>
<dt>B10_LOGGER_LOCALMSG</dt>
<dd>If defined, points to a local message file. The default is not to
use a local message file.</dd>
<dt>B10_LOGGER_DESTINATION</dt>
<dd>The location to which log message are written. This can be one of:
<ul>
<li><b>stdout</b> Message are written to stdout.</li>
<li><b>stderr</b> Messages are written to stderr.</li>
<li><b>syslog[:facility]</b> 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.)</li>
<li><b>Anything else</b> Interpreted as the name of a file to which
output is appended. If the file does not exist, a new one is opened.</li>
</ul>
In the case of "stdout", "stderr" and "syslog", they must be written exactly
as is - no leading or trailing spaces, and in lower-case.</dd>
</dl>
@subsection logInitializationPython Python Initialization
To initialize the logger in a Python program, the "init" method must be
called:
@code
isc.log.init(name, severity, debuglevel, file, buffer)
@endcode
<dl>
<dt><code>name</code></dt>
<dd>String giving the name of the root logger. This is the only mandatory
argument, the rest are optional.</dd>
<dt><code>severity</code></dt>
<dd>The severity, and is one of the strings "DEBUG", INFO" etc.
The default is "INFO".</dd>
<dt><code>debuglevel</code></dt>
<dd>Debug level, an integer between 0 and 99. A default value of 0 will
be used if this is not specified.</dd>
<dt><code>file</code></dt>
<dd>Name of the external message file (if present). By default, no
external message file is used.</dd>
<dt><code>buffer</code></dt>
<dd>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. By default, no buffer is used.</dd>
</dl>
@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:
<ol>
<li>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.</li>
<li>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.</li>
</ol>
*/