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

[trac1012] Add facility to create system messages manual

parent f3813c74
EXTRA_DIST = bind10-guide.css
EXTRA_DIST += bind10-guide.html
EXTRA_DIST += bind10-guide.html bind10-messages.html
EXTRA_DIST += bind10-guide.xml
# This is not a "man" manual, but reuse this for now for docbook.
if ENABLE_MAN
.PHONY: bind10-messages.html
bind10-guide.html: bind10-guide.xml
xsltproc --novalid --xinclude --nonet \
--path $(top_builddir)/doc \
......@@ -13,4 +15,8 @@ bind10-guide.html: bind10-guide.xml
http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
$(srcdir)/bind10-guide.xml
# So many dependencies that it's easiest just to regenerate it every time
bind10-messages.html:
$(PYTHON) $(top_srcdir)/tools/system_messages.py -o $@ $(top_srcdir)
endif
......@@ -1433,6 +1433,74 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
</chapter>
<chapter id="logging">
<title>Logging</title>
<!-- TODO: how to configure logging, logging destinations etc. -->
<para>
Each message written by BIND10 to the configured logging destinations
comprises a number of components that identify the origin of the
message and, if the message indicates a problem, information about the
problem that may be useful in fixing it.
</para>
<para>
Consider the message below logged to a file (the layout of messages
written to the system logging file (syslog) may be slightly different).
<screen>2011-06-15 13:48:22.034 ERROR [b10-resolver.asiolink]
ASIODNS_OPENSOCK, error 111 opening TCP socket to 127.0.0.1(53)</screen>
(The error has been split across two lines here for display reasons.
In the logging file, it will appear on one line.) The log message
comprises a number of components:
<variablelist>
<varlistentry>
<term>2011-06-15 13:48:22.034</term>
<listitem><para>
The date and time at which the message was generated.
</para></listitem>
</varlistentry>
<varlistentry>
<term>ERROR</term>
<listitem><para>
The severity of the message.
</para></listitem>
</varlistentry>
<varlistentry>
<term>[b10-resolver.asiolink]</term>
<listitem><para>
The source of the message. This comprises two components: the
BIND-10 process generating the message (in this case, the resolver
b10-resolver) and the module within the program from which the
message originated (which in the example is the asynchronous I/O
link module, asiolink).
</para></listitem>
</varlistentry>
<varlistentry>
<term>ASIODNS_OPENSOCK</term>
<listitem><para>
The message identification. Every message in BIND-10 has a unique
identification, which can be used as an index into the
<ulink url="bind10-messages.html">BIND10 Messages Manual</ulink>
from which more information can be obtained.
</para></listitem>
</varlistentry>
<varlistentry>
<term>error 111 opening TCP socket to 127.0.0.1(53)</term>
<listitem><para>
A brief description of the cause of the problem. Within this text,
information relating to the condition that caused the message to
be logged will be included. In this example, error number 111
(an operating system-specific error number) was encountered when
trying to open a TCP connection to port 53 on the local system
(address 127.0.0.1). The next step would be to find out the reason
for the failure by consulting your system's documentation to
identify what error number 111 means.
</para></listitem>
</varlistentry>
</variablelist>
</para>
</chapter>
<!-- TODO: how to help: run unit tests, join lists, review trac tickets -->
<!-- <index> <title>Index</title> </index> -->
......
# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# Produce System Messages Manual
#
# This tool reads all the .mes files in the directory tree whose root is given
# on the command line and interprets them as BIND 10 message files. It pulls
# all the messages and description out, sorts them by message ID, and writes
# them out as a single (formatted) file.
#
# Invocation:
# The code is invoked using the command line:
#
# python system_messages.py [-o <output-file>] <top-source-directory>
#
# If no output file is specified, output is written to stdout.
import re
import os
import sys
from optparse import OptionParser
# Main dictionary holding all the messages. The messages are accumulated here
# before being printed in alphabetical order.
dictionary = {}
# The structure of the output page is:
#
# header
# message
# separator
# message
# separator
# :
# separator
# message
# trailer
#
# (Indentation is not relevant - it has only been added to the above
# illustration to make the structure clearer.) The text of these section is:
# Header - this is output before anything else.
SEC_HEADER = """<html>
<head>
<title>BIND 10 System Messages</title>
<link rel="stylesheet" href="./bind10-guide.css" type="text/css">
</head>
<body>
<h1>BIND 10 System Messages</h1>
<p/>
"""
# This is output once for each message. The string contains substitution
# tokens: $I is replaced by the message identification, $T by the message text,
# and $D by the message description.
SEC_MESSAGE = """<b><a name="$I">$I</a></b>, $T<br/>
$D"""
# A description may contain blank lines intended to separate paragraphs. If so,
# each blank line is replaced by the following.
SEC_BLANK = "<p/>"
# The separator is copied to the output verbatim after each message except
# the last.
SEC_SEPARATOR = "<p/>"
# The trailier is copied to the output verbatim after the last message.
SEC_TRAILER = """</body>
</html>"""
def reportError(filename, what):
"""Report an error and exit"""
print("*** ERROR in ", filename, file=sys.stderr)
print("*** REASON: ", what, file=sys.stderr)
print("*** System message generator terminating", file=sys.stderr)
sys.exit(1)
# Printing functions
def printHeader():
print(SEC_HEADER)
def printSeparator():
print(SEC_SEPARATOR)
def printMessage(msgid):
m1 = SEC_MESSAGE.replace("$I", msgid)
m2 = m1.replace("$T", dictionary[msgid]['text'])
m3 = m2.replace("$D", dictionary[msgid]['description'])
print(m3)
def printTrailer():
print(SEC_TRAILER)
def replaceBlankLines(lines):
"""Replaces blank lines in an array with the contents of the 'blank'
section.
"""
result = []
for l in lines:
if len(l) == 0:
result.append(SEC_BLANK)
else:
result.append(l)
return result
def removeEmptyLeadingTrailing(lines):
"""Removes leading and trailing empty lines.
A list of strings is passed as argument, some of which may be empty.
This function removes from the start and end of list a contiguous
sequence of empty lines and returns the result. Embedded sequence of
empty lines are not touched.
Parameters:
lines List of strings to be modified.
Return:
Input list of strings with leading/trailing blank line sequences
removed.
"""
retlines = []
# Dispose of degenerate case of empty array
if len(lines) == 0:
return retlines
# Search for first non-blank line
start = 0
while start < len(lines):
if len(lines[start]) > 0:
break
start = start + 1
# Handle case when entire list is empty
if start >= len(lines):
return retlines
# Search for last non-blank line
finish = len(lines) - 1
while finish >= 0:
if len(lines[finish]) > 0:
break
finish = finish - 1
retlines = lines[start:finish + 1]
return retlines
def addToDictionary(msgid, msgtext, desc, filename):
"""Add the current message ID and associated information to the global
dictionary. If a message with that ID already exists, loop appending
suffixes of the form "(n)" to it until one is found that doesn't.
Parameters:
msgid Message ID
msgtext Message text
desc Message description
filename File from which the message came. Currently this is
not used, but a future enhancement may wish to include the
name of the message file in the messages manual.
"""
# If the ID is in the dictionary, append a "(n)" to the name - this wil
# flag that there are multiple instances. (However, this is an error -
# each ID should be unique in BIND-10.)
if msgid in dictionary:
i = 1
while msgid + " (" + str(i) + ")" in dictionary:
i = i + 1
msgid = msgid + " (" + str(i) + ")"
# Remove leading and trailing blank lines, and replace embedded blanks
# with the blank section element.
modified_desc = replaceBlankLines(removeEmptyLeadingTrailing(desc))
# Put everything in a sub-dictionary that is added to the main one. At
# this point, for ease of subsequent processing the description lines are
# concatenated together to form a single string, the lines being separated
# by a newline.
details = {}
details['text'] = msgtext
details['description'] = "\n".join(modified_desc)
details['filename'] = filename
dictionary[msgid] = details
def processFileContent(filename, lines):
"""Processes file content. Messages and descriptions are identified and
added to a dictionary (keyed by message ID). If the key already exists,
a numeric suffix is added to it.
Parameters:
filename Name of the message file being processed
lines Lines read from the file
"""
prefix = "" # Last prefix encountered
msgid = "" # Last message ID encountered
msgtext = "" # Text of the message
description = [] # Description
for l in lines:
if l.startswith("$"):
# Starts with "$". Ignore anything other than $PREFIX
words = re.split("\s+", l)
if words[0].upper() == "$PREFIX":
if len(words) == 1:
prefix = ""
else:
prefix = words[1]
elif l.startswith("%"):
# Start of a message. Add the message we were processing to the
# dictionary and clear everything apart from the file name.
if msgid != "":
addToDictionary(msgid, msgtext, description, filename)
msgid = ""
msgtext = ""
description = []
# Start of a message
l = l[1:].strip() # Remove "%" and trim leading spaces
if len(l) == 0:
printError(filename, "Line with single % found")
next
# Split into words. The first word is the message ID
words = re.split("\s+", l)
msgid = (prefix + words[0]).upper()
msgtext = l[len(words[0]):].strip()
else:
# Part of a description, so add to the current description array
description.append(l)
# All done, add the last message to the global dictionaty.
if msgid != "":
addToDictionary(msgid, msgtext, description, filename)
#
# \param file Name of the file to process
def processFile(filename):
"""Processes a file by reading it in and stripping out all comments and
and directives. Leading and trailing blank lines in the file are removed
and the remainder passed for message processing.
Parameters:
filename Name of the message file to process
"""
lines = open(filename).readlines();
# Trim leading and trailing spaces from each line, and remove comments.
lines = [l.strip() for l in lines]
lines = [l for l in lines if not l.startswith("#")]
# Remove leading/trailing empty line sequences from the result
lines = removeEmptyLeadingTrailing(lines)
# Interpret content
processFileContent(filename, lines)
def processAllFiles(root):
"""Iterates through all files in the tree starting at the given root and
calls processFile for all .mes files found.
Parameters:
root Directory that is the root of the BIND-10 source tree
"""
for (path, dirs, files) in os.walk(root):
# Identify message files
mes_files = [f for f in files if f.endswith(".mes")]
# ... and process each file in the list
for m in mes_files:
processFile(path + os.sep + m)
# Main program
if __name__ == "__main__":
parser = OptionParser(usage="Usage: %prog [--help | options] root")
parser.add_option("-o", "--output", dest="output", default=None,
metavar="FILE",
help="output file name (default to stdout)")
(options, args) = parser.parse_args()
if len(args) == 0:
parser.error("Must supply directory at which to begin search")
elif len(args) > 1:
parser.error("Only a single root directory can be given")
# Redirect output if specified (errors are written to stderr)
if options.output is not None:
sys.stdout = open(options.output, 'w')
# Read the files and load the data
processAllFiles(args[0])
# Now just list the message IDs and text in the global dictionary
count = 1
printHeader()
for msgid in sorted(dictionary):
if count > 1:
printSeparator()
count = count + 1
printMessage(msgid)
printTrailer()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment