Using log4net
by Nauman Leghari06/16/2003
Introduction
Logging is an essential tool in every developer's arsenal. It helps the developer to
identify problems faster by showing the state of an application at any given
point. It is important after deployment, when all that the poor system admins have are the logs that are generated by your application.
So it is absolutely necessary to be equipped with a logging framework which
is easy to set up, easy to use, and extensible. With this in mind, we will
be discussing log4net, an open source logging and tracing framework. The only
prerequisite for this article is to know how to program in .NET using C#, although
the concepts are applicable to programmers using VB.NET or any other .NET language.
About log4net
log4net, as I said earlier, is an open source project and is the port of the
famous log4j project for Java. It is an excellent piece of work, started by a
team at www.neoworks.com, but it
would not have been possible without the contributions made by the community. log4net provides many advantages over other logging
systems, which makes it a perfect choice for use in any type of application,
from a simple single-user application to a complex multiple-threaded
distributed application using remoting. The complete features list can be viewed
here.
It can be downloaded from the web site
under the Apache license. The latest version at this writing is 1.2.0 beta
7, upon which this article is based. The changes in this release are listed here.
You can see from the feature document that this framework is released for
four different platforms. There are separate builds for Microsoft .NET Framework,
Microsoft .NET Compact Framework, Mono 0.23, and SSCLI 1.0. There are different
levels of support provided with each framework, the details of which are documented here.
This version of log4net is provided with NAnt
build scripts. To compile the framework, you can execute the build.cmd file
from the root directory where you extracted the zipped file. The log4net.sln
file in the <log4net-folder>\src directory is the solution file for log4net
source, whereas the examples are provided in a separate solution file in
<log4net-folder>\examples\net\1.0. The samples are provided
in C#, VB.NET, VC++.NET, and even in JScript.NET. Some of the samples have their
configuration files in the project's root folder, so in order to run those samples
you need to manually move them with project's executable file. The API documentation
is provided in the <log4net-folder>\doc\sdk\net directory.
The
Structure of log4net
log4net is built using the layered approach, with four main components inside
of the framework. These are Logger, Repository, Appender, and Layout.
Logger
The Logger is the main component with which your application interacts. It is also the component that generates the log messages.
Generating a log message is different than actually showing the final output. The output is showed by the Layout component, as we will see later.
The logger provides you with different methods to log any message. You can
create multiple loggers inside of your application. Each logger that you instantiate
in your class is maintained as a "named entity" inside of the log4net framework.
That means that you don't need to pass around the Logger instance between
different classes or objects to reuse it. Instead, you can call it with the
name anywhere in the application. The loggers maintained inside of the framework
follow a certain organization. Currently, the log4net framework uses the hierarchical
organization. This hierarchy is similar to the way we define namespaces in .NET.
For example, say there are two loggers, defined as a.b.c and a.b.
In this case, the logger a.b is said to be the ancestor of the logger
a.b.c. Each logger inherits properties from its parent logger.
At the top of the hierarchy is the default logger, which is also called the
root logger, from which all loggers are inherited. Although this namespace-naming scheme is preferred in most scenarios, you are allowed to name your
logger as you would like.
The log4net framework defines an interface, ILog, which is necessary for all
loggers to implement. If you want to implement a custom logger, this
is the first thing that you should do. There are a few examples in the /extension
directory to get you started.
The skeleton of the ILog interface is shown below:
public interface ILog
{
void Debug(object message);
void Info(object message);
void Warn(object message);
void Error(object message);
void Fatal(object message);
// There are overloads for all of the above methods which
// supports exceptions. Each overload in that case takes an
// addition parameter of type Exception like the one below.
void Debug(object message, Exception ex);
// ...
// ...
// ...
// The Boolean properties are used to check the Logger's
// level (as we'll see Logging Levels in the next section)
bool isDebugEnabled;
bool isInfoEnabled;
// other boolean properties for each method
}
From this layer, the framework exposes a class called LogManager, which manages
all loggers. It has a GetLogger() method that retrieves the logger for us against
the name we provided as a parameter. It will also create the logger for us if
it is not already present inside of the framework.
log4net.ILog log = log4net.LogManager.GetLogger("logger-name");
Most often, we define the class type as the parameter to track the name of
the class in which we are logging. The name that is passed is prefixed with
all of the log messages generated with that logger. The type of class can be passed
in by name using the typeof(Classname) method, or it can be retrieved through
reflection by the following statement:
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType
Despite the long syntax, the latter is used in the samples for its portability, as you can copy the same statement anywhere to get the class in which it is used.
Logging Levels
As you can see in the ILog interface, there are five different methods for
tracing an application. Why do we need all of these different methods? Actually,
these five methods operate on different levels of priorities set for the logger.
These different levels are defined as constants in the log4net.spi.Level class.
You can use any of the methods in your application, as appropriate.
But after using all of those logging statements, you don't want to have all of
that code waste CPU cycles in the final version that is deployed. Therefore,
the framework provides seven levels and their respective Boolean properties
to save a lot of CPU cycles. The value of Level can be one of the following:
Table 1. Different Levels of a Logger
Level |
Allow Method | Boolean Property | Value |
|
|
Highest |
||
|
|
void Fatal(...); |
bool IsFatalEnabled; |
|
|
|
void Error(...); |
bool IsErrorEnabled; |
|
|
|
void Warn(...); |
bool IsWarnEnabled; |
|
|
|
void Info(...); |
bool IsInfoEnabled; |
|
|
|
void Debug(...); |
bool IsDebugEnabled; |
|
|
|
Lowest |
In the log4net framework, each logger is assigned a priority level (which is
one of the values from the table above) through the configuration settings.
If a logger is not assigned a Level, then it will try to inherit the Level value
from its ancestor, according the hierarchy.
Also, each method in the ILog interface has a predefined value of its level.
As you can see in Table 1, the Info() method of the ILog interface has the
INFO level. Similarly, the Error() method has the ERROR level, and so on. When we
use any of these methods, the log4net framework checks the method level against
the level of the logger. The logging request is said to be enabled if the logger's
level is greater than or equal to the level of the
logging method.
For example, let's say you create a logger object and set it to the level of
INFO. The framework then sets the individual Boolean properties for that logger.
The level checking is performed when you call any of the logging methods.
Logger.Info("message");
Logger.Debug("message");
Logger.Warn("message");
For the first method, the level of method Info() is equal to the level set
on the logger (INFO), so the request passes through and we get the output,
"message."
For the second method, the level of the method Debug() is less than that of the logger (see Table 1). There, the request is disabled or refused and you get
no output.
Similarly, you can easily conclude what would have happened in the third line.
There are two special Levels defined in Table 1. One is ALL, which enables
all requests, and the other is OFF, which disables all requests.
You can also explicitly check the level of the logger object through the Boolean properties.
if (logger.IsDebugEnabled)
{
Logger.Debug("message");
}

