Logging (log4e)
The log4e library provides a complete framework
for logging messages in an application. Logged messages can be processed
based on priority and content and sent to multiple destinations. A destination,
in Goanna log4e, is known as an appender. The types of appenders include
files and the NT event log.
The classes in log4e are a direct port of Jakarta log4j
which can be found at the Apache
Jakarta project. For the most part, the source
has been ported as is. A number of Eiffel-like improvements have been made
such as appropriate contracts, and the use of generic types. The general
struture and usage of the library remains very similar to the original
Java version.
Overview
The central object of the log4e library is the
hierarchy. The hierarchy holds each of the
logging categories on which messages
can be logged. As its name suggests, the hierarchy maintains the
collection of categories in a hierarchy with a predefined root
category at the top and user defined sub-categories beneath.
Each category has a name, a logging priority and zero or
more appenders. Different types
of appenders can log messages to different destinations, be it
a file, NT event log or socket. They can also format those
messages any way required and filter messages based on priority
and/or message content.
The Category Hierarchy
The category hierarchy, represented by the class
LOG_HIERARCHY, holds a collection of
categories that you define. Multiple independant hierarchies can exist within
an application. However, it is more common
to use one hierarchy with multiple categories to represent different
logging areas.
LOG_HIERARCHY provides methods for accessing
categories and setting hierarchy level
logging priorities. The hierarchy is the only object that can create
instances of categories
(LOG_CATEGORY and maintains the parents
of each category internally. In addition, each hierarchy
instance initialises a root category that resides at
the top of the category hierarchy. If required,
the root category can be used as the sole logging category in an
application.
Categories
The category hierarchy is determine by the names given to
each category — a dot in the name indicates a
parent-child relationship between categories. For example,
if a category is created with the name
a.b it identifies the category named
b with the parent category named
a. When this category is created the category
hierarchy will also instantiate any intermediate
parent categories if needed. For the category named
a.b the hierarchy will create a category
named a if it doesn't already exist and set
it as the parent of b. A category
name can contain an arbitrary number of parts separated by
dots ('.'). The hierarchy will manage the parents
automatically for you.
The naming of categories follows your requirements for
logging. Category hierarchies can be defined that
provide logging categories for an application as a whole,
subsystems and/or source modules. You can create very simple
logging hierarchies, such as using only the root category,
through to very complex hierarchies, such as providing a
category for every source file or module in your application.
A category can only be created by the category hierarchy by
calling the category
function of LOG_CATEGORY. The sole
parameter to this function specifies the name of the category
you would like to access. The hierarchy will create the
category (and its parents) if required and return the category
to you. This ensures that there will only be one instance
of a named category in each category hierarchy and that it is
correctly positioned in the hierarchy. Once you have a
reference to the category you can then configure the category's
priority and appenders and log messages to it. For example, given the
following variables declarations:
hierarchy: LOG_HIERARCHY
category: LOG_CATEGORY
you can create a category named server.access and
assign it to the variable category using:
category := hierarchy.category ("server.access")
Messages can be sent to a category using the routines
debuggingThe original
log4j library uses the routine name
debug however this is a reserved word
in Eiffel and has therefore been renamed
debugging,
warn, info,
error, fatal or
log to log at predefined priorities or
a specified priority in the case of log.
Priorities
Each log message has a priority which can determine
what should be done with the message, whether it should be written to a
log file or discarded. All log4e priorities are defined in the class
LOG_PRIORITY_CONSTANTS of type
LOG_PRIORITY and range from the lowest priority
Debug_p through Info_p,
Warning_p and Error_p, to the
highest priority Fatal_p. The logging priorities are
represented by a positive integer ranging from 50,000 for
Fatal_p through to 10,000 for
Debug_p.
The integers representing
priorities could just as easily ranged from 5 to 1. The numbers were
originally used in the log4j library and
it was decided not to change them.
This is to facilitate comparisons
between priorities to determine if a log message matches or exceeds a set
priority and should therefore be written to a log file.
A log priority also has a string representation that identifies the
priority and can be written to log files. For example, the
Debug_p has the string representation
DEBUG which can be inserted into a log message during
the formatting process.
Each category can have its own priority setting which determines the
level of log messages that will be processed or ignored. If a category
does not have a priority set it will inherit one from its parent. The
category priority determines the level of logging that will take place
on that category — all log messages that meet or exceed the
priority will be logged, all others will be discarded.
To set the priority of a category you need to call
set_priority and pass a priority object from the
class LOG_PRIORITY_CONSTANTS. For example, you
can set the logging priority of the category defined above to
Info_p using:
category.set_priority (Info_p)
after which you can access the category's priority using the function
priority which will return the priority or the
parent category's priority if one has not been set.
In addition to priorities set at the level of categories, a priority can
be set at the hierarchy level. A hierarchy's priority is set during
initialisation. The creation procedure make
specifies a single argument, priority, which is used
to set its priority level. The hierarchy priority level is checked
before any category level priority to determine if a log message should
be handled.
Fine grained control over priorities can also be performed at the
hierarchy level by disabling a particular priority (and and priorities
below it). When a hierarchy is first created all priorities are enabled
by default. If you need to disable a priority for an entire hierarchy,
the most efficient way is to call disable of
LOG_HIERARCHY passing the priority to
disable. Convenience routines are also included to disable the most
common priorities including disable_debug and
disable_info used to disable debugging and
information logging, respectively. You can also enable or disable all
levels of logging using the functions enable_all
and disable_all.
The logging hierarchy typically determines whether to handle a message
at a particular priority using internal integer comparisons. You can
also determine whether a log message will be handled by calling
is_enabled_for and passing the relevant priority
object. The function will return True if that level
of logging or above is enabled.
Additive Categories
Once a category has handled a log message it can pass it on to its parent
for additional handling. This is known as an
additive category. The parent can then handle the
log message and pass on to its parent if it is also additive. A category
is additive by default as indicated by the query
is_additive. If you need to change the additive
status of a category call set_additive passing
True or False depending on the
status you need.
The following table shows the affect of the
is_additive flag.
Affect of is_additive flag.
Category Name
is_additive
Handled by
root
not applicable
root
x
True
x, root
x.y
True
x, y and
root
x.y.z
True
x, y,
z and root
security
False
security
security.access
True
access and
security
Logging
To send a log message of each priority to the category we created earlier,
you can write:
category.debugging ("This is a debug message")
category.info ("This is an information message")
category.warn ("This is a warning message")
category.error ("This is an error message")
category.fatal ("This is a fatal message")
category.log (hierarchy.Info_p, "This is another information message")
Each of the statements send a message to the
server.access category with different priorities. The
final statement explicitly logs a message with the priority
Info_p as defined in
LOG_PRIORITY_CONSTANTS. Many of the log4e classes
inherit (sometimes indirectly) from
LOG_PRIORITY_CONSTANTS and therefore provide
access to all of the predefined priority constants, as used in the
log example above. We could just as easily
inherited from the LOG_PRIORITY_CONSTANTS class
to access the priority directly.
All of the logging routines take an object of type
ANY as the message
parameter thus allowing objects of arbitrary types to be logged. The
category calls out on the object to convert it to a
string that can then be written to the appropriate log. Basic types,
such as INTEGER, DOUBLE
and CHARACTER, all define reasonable
implementations of out. If you need to log
objects other than basic types you will need to redefine
out to return the
STRING representation you require. For example,
given the following class USER:
class USER
inherit
ANY
redefine out end
creation
make
feature -- Initialisation
make (first, last: STRING) is
do
first_name := first
surname := last
end
feature -- Access
first_name: STRING
surname: STRING
feature -- Basic routines
out: STRING is
do
create Result.make (100)
Result.append ("USER first_name=" + first_name)
Result.append (" surname=" + surname)
end
end -- class USER
We can create an instance and log details about that instance to a
category by passing the instance itself:
create user.make ("Jim", "Smith")
category.info (user)
The string that will be sent to the log will be: USER
first_name=Jim surname=Smith
Appenders
Describe the purpose of appenders and how to create one.
File Appenders
Describe general file appender classes.
Standard Out and Standard Error
Describe standard out and standard error file appenders.
Rolling Files
Describe rolling file appenders: rolling file, calendar
rolling file and externally rolled.
NT Event Log
Describe logging to the NT Event log.
Appender Layouts
Describe creation and usage of layouts.
Simple Layout
Describe simple layouts.
Date/Time Layout
Describe time layout and date time layouts.
Pattern Layout
Describe pattern layouts.
Filters
Describe filtering of log events.
Priority Match Filters
Describe priority match filters.
Priority Range Filters
Describe priority range filters.
String Match Filters
Describe string match filters.
Configuration
Describe general configuration.
Programmatic Configuration
Describe general programmatic configuration and shared logging hierarchies.
XML Configuration
Describe configuration using XML.