Apache log4net™ Manual - Introduction

Overview

This document is based on Short introduction to log4j by Ceki Gülcü.

The log4net framework is based on Apache log4j™, see
http://logging.apache.org/log4j/ for more information on log4j.
The log4net framework, source code, binaries, documentation, examples and related
materials are published under the terms of the
Apache License, Version 2.0,
a copy of which has been included with this distribution in the LICENSE.txt file.

This document is an introduction to the log4net API, its unique features and
design rationale. Log4net is an open source project based on the work of many
authors. It allows the developer to control which log statements are output
with arbitrary granularity. It is fully configurable at runtime using external
configuration files.

Almost every large application includes its own logging or tracing API.
Inserting log statements into code is a low-tech method for debugging it. It
may also be the only way because debuggers are not always available or
applicable. This is usually the case for multithreaded applications and
distributed applications at large.

Once an application has been deployed it may not be possible to utilize
development and debugging tools. An administrator can use effective logging
systems to diagnose and fix many configuration issues.

Experience indicates that logging is an important component of the development
cycle. It offers several advantages. It provides precise context about the
execution of the application. Once inserted into the code, the generation of
logging output requires no human intervention. Moreover, log output can be
saved in persistent medium to be studied at a later time. In addition to its
use in the development cycle, a sufficiently rich logging package can also be
viewed as an auditing tool.

Logging does have its drawbacks. It can slow down an application. If too
verbose, it can cause scrolling blindness. To alleviate these concerns, log4net
is designed to be reliable, fast and extensible. Since logging is rarely the
main focus of an application, the log4net API strives to be simple to
understand and to use.

Frameworks

Log4net is available for several frameworks. For each supported framework an
assembly targeting that framework is built:

.NET Standard 1.3 via .NET Core 1.0

Microsoft® .NET Framework 1.0

Microsoft .NET Framework 1.1

Microsoft .NET Framework 2.0

Microsoft .NET Framework 3.5

Microsoft .NET Framework 4.0

Microsoft .NET Framework 4.5

Microsoft .NET Framework 3.5 Client Profile

Microsoft .NET Framework 4.0 Client Profile

Microsoft .NET Compact Framework 1.0

Microsoft .NET Compact Framework 2.0

Mono 1.0

Mono 2.0

Mono 3.5

Mono 4.0

Microsoft Shared Source CLI 1.0

CLI 1.0 Compatible

Not all frameworks are created equal and some features have been excluded from
some of the builds. See the Framework Support
document for more information.

Loggers and Appenders

Log4net has three main components: loggers, appenders and layouts.
These three types of components work together to enable developers to log
messages according to message type and level, and to control at runtime how
these messages are formatted and where they are reported. These components are
helped by filters that control the actions of the appender and
object renderers that turn objects into strings.

Logger hierarchy

The first and foremost advantage of any logging API over plain
System.Console.WriteLine
resides in its ability to disable certain log statements while allowing others
to print unhindered. This capability assumes that the logging space, that is,
the space of all possible logging statements, is categorized according to some
developer-chosen criteria.

Loggers are named entities. Logger names are case-sensitive and they follow the
following hierarchical naming rule:

Named Hierarchy

A logger is said to be an ancestor of another logger if its name
followed by a dot is a prefix of the descendant logger name. A logger is
said to be a parent of a child logger if there are no ancestors
between itself and the descendant logger.

The hierarchy works very much in the same way as the namespace and class
hierarchy in .NET. This is very convenient as we shall soon see.

For example, the logger named
"Foo.Bar"
is a parent of the logger named
"Foo.Bar.Baz". Similarly,
"System"
is a parent of
"System.Text"
and an ancestor of
"System.Text.StringBuilder". This naming scheme
should be familiar to most developers.

The root logger resides at the top of the logger hierarchy. It is exceptional
in three ways:

It always exists

It cannot be retrieved by name

It always has an assigned level

Loggers are retrieved using the static method from the
log4net.LogManager
class. The
GetLogger
methods take the name of the desired logger as a parameter. They are listed
below:

Loggers may be assigned levels. Levels are instances of the
log4net.Core.Level
class. The following levels are defined in order of increasing priority:

ALL

DEBUG

INFO

WARN

ERROR

FATAL

OFF

If a given logger is not assigned a level, then it inherits one from its
closest ancestor with an assigned level. More formally:

Level Inheritance

The inherited level for a given logger X, is equal to the first
non-null level in the logger hierarchy, starting at X and proceeding
upwards in the hierarchy towards the root logger.

To ensure that all loggers can eventually inherit a level, the root logger
always has an assigned level. The default value for the root logger is
DEBUG.

Below are four tables with various assigned level values and the resulting
inherited levels according to the above rule.

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

none

Proot

X.Y

none

Proot

X.Y.Z

none

Proot

In Example 1 above, only the root logger is assigned a level. This level
value,
Proot, is inherited by the other loggers
X,
X.Y
and
X.Y.Z.

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

Px

Px

X.Y

Pxy

Pxy

X.Y.Z

Pxyz

Pxyz

In Example 2 above, all loggers have an assigned level value. There is
no need for level inheritance.

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

Px

Px

X.Y

none

Px

X.Y.Z

Pxyz

Pxyz

In Example 3 above, the loggers
root,
X
and
X.Y.Z
are assigned the levels
Proot,
Px
and
Pxyz
respectively. The logger
X.Y
inherits its level value from its parent
X.

Logger name

Assigned level

Inherited level

root

Proot

Proot

X

Px

Px

X.Y

none

Px

X.Y.Z

none

Px

In Example 4 above, the loggers root and
X
and are assigned the levels
Proot
and
Px
respectively. The loggers
X.Y
and
X.Y.Z
inherits their level value from their nearest parent
X
having an assigned level.

Logging requests are made by invoking one of the printing methods of a logger
instance (through the log4net.ILog). These printing methods are
Debug,
Info,
Warn,
Error, and
Fatal.

By definition, the printing method determines the level of a logging request.
For example, if
log
is a logger instance, then the statement
log.Info("..")
is a logging request of level INFO.

A logging request is said to be enabled if its level is higher than or
equal to the level of its logger. Otherwise, the request is said to be disabled.
A logger without an assigned level will inherit one from the hierarchy. This
rule is summarized below.

Basic Selection Rule

A log request of level L in a logger with (either assigned or inherited,
whichever is appropriate) level K, is enabled if L >= K.

This rule is at the heart of log4net. It assumes that levels are ordered. For
the standard levels, we have
DEBUG < INFO < WARN < ERROR < FATAL.

Calling the
log4net.LogManager.GetLogger
method with the same name will always return a reference to the exact same
logger object.

Thus, it is possible to configure a logger and then to retrieve the same
instance somewhere else in the code without passing around references. In
fundamental contradiction to biological parenthood, where parents always
precede their children, log4net loggers can be created and configured in any
order. In particular, a "parent" logger will find and link to its descendants
even if it is instantiated after them.

Configuration of the log4net environment is typically done at application
initialization. The preferred way is by reading a configuration file. This
approach will be discussed shortly.

Log4net makes it easy to name loggers by software component. This can be
accomplished by statically instantiating a logger in each class, with the
logger name equal to the fully qualified name of the class. This is a useful
and straightforward method of defining loggers. As the log output bears the
name of the generating logger, this naming strategy makes it easy to identify
the origin of a log message. However, this is only one possible, albeit common,
strategy for naming loggers. Log4net does not restrict the possible set of
loggers. The developer is free to name the loggers as desired.

Nevertheless, naming loggers after the class where they are located seems to be
the best strategy known so far. It is simple an obvious to the developers where
each log message came from. Most importantly it leverages the design of the
application to produce the design of the logger hierarchy. Hopefully some
thought has gone into the design of the application.

Appenders

The ability to selectively enable or disable logging requests based on their
logger is only part of the picture. Log4net allows logging requests to print to
multiple destinations. In log4net speak, an output destination is called an appender.
Appenders must implement the log4net.Appenders.IAppender
interface.

Writes logging events to the application's Console. The events may go to either
the standard our stream or the standard error stream. The events may have configurable
text and background colors defined for each level.

Writes logging events to the debugger. If the application has no
debugger, the system debugger displays the string. If the application has no
debugger and the system debugger is not active, the message is ignored.

Sends logging events as connectionless UDP datagrams to a remote host or a
multicast group using a UdpClient.

More than one appender can be attached to a logger.

Each enabled logging request for a given logger will be forwarded to all
the appenders in that logger as well as the appenders higher in the hierarchy.
In other words, appenders are inherited additively from the logger hierarchy.
For example, if a console appender is added to the root logger, then all
enabled logging requests will at least print on the console. If in addition a
file appender is added to a logger, say X, then enabled logging requests
for X and X's children will print on a file and on the
console. It is possible to override this default behavior so that appender
accumulation is no longer additive by setting the additivity flag on the logger
to
false.

The rules governing appender additivity are summarized below.

Appender Additivity

The output of a log statement of logger X will go to all the appenders
in X and its ancestors. This is the meaning of the term "appender
additivity".

However, if an ancestor of logger X, say Y, has the additivity
flag set to
false, then X's output will be directed to all
the appenders in X and it's ancestors up to and including Y but
not the appenders in any of the ancestors of Y.

Loggers have their additivity flag set to
true
by default.

The table below shows an example:

Logger Name

Added Appenders

Additivity Flag

Output Targets

Comment

root

A1

not applicable

A1

There is no default appender attached to root.

x

A-x1, A-x2

true

A1, A-x1, A-x2

Appenders of "x" and root.

x.y

none

true

A1, A-x1, A-x2

Appenders of "x" and root.

x.y.z

A-xyz1

true

A1, A-x1, A-x2, A-xyz1

Appenders in "x.y.z", "x" and root.

security

A-sec

false

A-sec

No appender accumulation since the additivity flag is set to
false.

security.access

none

true

A-sec

Only appenders of "security" because the additivity flag in "security" is set
to
false.

Filters

Appenders can filter the events that are delivered to them. The filters can be
specified in the configuration to allow fine control of the events that are
logged through different appenders.

The simplest form of control is to specify a
Threshold
on the appender. This works by logging only the events that have a level that
is greater than or equal to the threshold.

More complex and custom event filtering can be done using the filter chain
defined on each appender. Filters must implement the
log4net.Filter.IFilter interface.

The filters can be configured to either accept or reject the event based upon
the match.

Layouts

More often than not, users wish to customize not only the output destination
but also the output format. This is accomplished by associating a layout
with an appender. The layout is responsible for formatting the logging request
according to the user's wishes, whereas an appender takes care of sending the
formatted output to its destination. The
PatternLayout, part of the standard log4net
distribution, lets the user specify the output format according to conversion
patterns similar to the C language
printf
function.

The first field is the number of milliseconds elapsed since the start of the
program. The second field is the thread making the log request. The third field
is the level of the log statement. The fourth field is the name of the logger
associated with the log request. The text after the '-' is the message of the
statement.

Formats the logging event as an XML element that
complies with the log4j event dtd.

Object Renderers

Just as importantly, log4net will render the content of the log message
according to user specified criteria. For example, if you frequently need to
log
Oranges, an object type used in your current project,
then you can register an
OrangeRenderer
that will be invoked whenever an orange needs to be logged.

Object rendering follows the class hierarchy. For example, assuming oranges are
fruits, if you register an
FruitRenderer, all fruits including oranges will be
rendered by the
FruitRenderer, unless of course you registered an
orange specific
OrangeRenderer.

Object renderers have to implement the
log4net.ObjectRenderer.IObjectRenderer
interface.

Please note that ObjectRenderers are not used by the DebugFormat,
InfoFormat, WarnFormat,
ErrorFormat and FatalFormat methods.