 
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. 
log4netlog4net, 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.
log4netlog4net is built using the layered approach, with four main components inside 
  of the framework. These are Logger, Repository, Appender, and Layout.
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().DeclaringTypeDespite 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.
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");
}
| 
 | 
The second layer is responsible for maintaining the organization of loggers. 
  By organization, I am talking about the logical structure of the loggers inside of
  the framework. Before the current version of log4net, the framework only supported the hierarchical 
  organization. As we discussed earlier, this hierarchical nature is an implementation of 
  the repository and is defined in the log4net.Repository.Hierarchy namespace. 
  To implement a Repository, it is necessary to implement the log4net.Repository.ILoggerRepository 
  interface. But instead of directly implementing this interface, another 
  class, log4net.Repository.LoggerRepositorySkeleton, is provided to work as the   base class; e.g., the hierarchical repository is implemented by the log4net.Repository.Hierarchy.Hierarchy class.
If you are a normal developer only using the log4net framework instead of extending  it, then you would probably not use any of these Repository classes in your code. 
  Instead, you would use the LogManager class, as described earlier, to automatically 
  manage the repositories and the loggers.
Any good logging framework should be able to generate output for multiple destinations, 
  such as outputting the trace statements to the console or serializing it into a log file. 
  log4net is a perfect match for this requirement. It uses a component called 
  Appender to define this output medium. As the name suggests, these components append themselves 
  to the Logger component and relay the output to an output stream. You can append multiple appenders to a single logger. 
  There are several appenders provided by the log4net framework; the 
  complete list of appenders provided by the log4net framework can be found here.
With all of these appenders provided, there is not much need for writing 
  your own, but if you wish to, you can start by inheriting the log4net.Appender.AppenderSkeleton 
  class, which works as an adapter between your class and the IAppender interface.
An Appender defaults to pass all logging events to the Layout. Appender Filters 
  can be used to select events by different criteria. There are several filters 
  defined under the log4net.Filter namespace. By using a filter, you can either 
  filter a range of level values, or filter out any log message with a 
  particular string. We'll see filters in action later in our example. More information 
  about filters is provided in the API documentation.
The Layout component is used to display the final formatted output to the user. 
  The output can be shown in multiple formats, depending upon the layout we are 
  using. It can be linear or an XML file. The layout component works with an appender. 
  There is a list of different layouts in the API documentation. You cannot use 
  multiple layouts with an appender. To create your own layout, you need to inherit 
  the log4net.Layout.LayoutSkeleton class, which implements the ILayout interface.
log4net in Your ApplicationBefore you start logging your application, you need to heat up the log4net 
  engine. Technically, this means that you need to configure the three components 
  that we discussed earlier. There are two different methods by which you can 
  specify the configuration: you can either define them in a separate configuration 
  file, or you can place them inside of your code, configuring it programmatically.
The first method is always recommended, for the following reasons.
Considering the importance of the first method, we'll see it first.
The configuration settings required are put into either of the following files:
The log4net framework looks for the configuration file in the file path relative to the application's base directory defined by the AppDomain.CurrentDomain.BaseDirectory 
  property. The only thing that the log4net framework searches for inside of the configuration 
  file is the <log4net> tag. A complete sample configuration file is shown 
  below:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" 
      type="log4net.Config.Log4NetConfigurationSectionHandler,
            log4net-net-1.0" 
    />
  </configSections>
  <log4net>
    
    <logger name="testApp.Logging">
      <level value="DEBUG"/>
    </logger>
    
    <root>
      <level value="WARN" />
      <appender-ref ref="LogFileAppender" />
      <appender-ref ref="ConsoleAppender" />
    </root>
    
    <appender name="LogFileAppender" 
             type="log4net.Appender.FileAppender" >
      <param name="File" value="log-file.txt" />
      <param name="AppendToFile" value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <param name="Header" value="[Header]\r\n"/>
        <param name="Footer" value="[Footer]\r\n"/>
        <param name="ConversionPattern" 
           value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n"
         />
      </layout>
      <filter type="log4net.Filter.LevelRangeFilter">
        <param name="LevelMin" value="DEBUG" />
        <param name="LevelMax" value="WARN" />
      </filter>
    </appender>
    
    <appender name="ConsoleAppender" 
              type="log4net.Appender.ConsoleAppender" >
      <layout type="log4net.Layout.PatternLayout">
        <param name="ConversionPattern" 
           value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" 
        />
      </layout>
    </appender>
    
  </log4net>
</configuration>
You can copy the above file to use in any application, but it 
  is always better to know what constitutes the configuration file. The <section> 
  entry inside of the <configSection> tag is only necessary if you are using 
  the application's configuration file. Otherwise, only the text inside of the 
  <log4net> tag is required. It is not a requirement to maintain the sequence 
  of individual tags; I am only putting it this way to maintain the flow of describing 
  things. Taking each tag individually, we start with the <logger> element.
<Logger><logger name="testApp.Logging">
  <level value="DEBUG"/>
  <appender-ref ref="LogFileAppender" />
  <appender-ref ref="ConsoleAppender" />
</logger>
 The <logger> element defines the settings for an individual logger. Then 
  by calling LogManager.GetLogger(...), you can retrieve the same logger by the 
  name. You can also define the appenders to use with that logger through the 
  <appender-ref> tag. <appender-ref> defines a reference to an   appender which is actually defined anywhere else.
<root>
<root>
  <level value="WARN" />
  <appender-ref ref="LogFileAppender" />
  <appender-ref ref="ConsoleAppender" />
</root>
The <root> tag is next to the logger tag. All loggers in the hierarchy 
  are children of the root logger; therefore the framework uses the properties 
  defined here if there are no loggers explicitly defined in the configuration 
  file. After knowing this, we can also tell that the <logger> tag that 
  we see above is not necessary. Inside of the <root> tag, the default values 
  are defined. Both the level value and the appender list can be put in 
  here. The default value of LEVEL, if not defined anywhere else, is set to DEBUG. Obviously, 
  the individual setting for a logger in the <logger> tag overrides the 
  settings in the root tag for that particular logger. In the case of an appender, 
  the <logger> tag will inherit all of the appenders defined by its ancestor. 
  This default behavior can be changed by explicitly setting the additivity 
  attribute for the <logger> tag to false. 
<logger name="testApp.Logging" additivity="false">
</logger>
This attribute is set to true by default. The <root> tag is not necessary, 
  but recommended.
<appender>
<appender name="LogFileAppender" 
          type="log4net.Appender.FileAppender" >
  <param name="File" value="log-file.txt" />
  <param name="AppendToFile" value="true" />
  <layout type="log4net.Layout.PatternLayout">
    <param name="Header" value="[Header]\r\n" />
    <param name="Footer" value="[Footer]\r\n"/>
    <param name="ConversionPattern" 
      value="%d [%t] %-5p %c [%x] <%X{auth}> - %m%n" 
    />
  </layout>
  <filter type="log4net.Filter.LevelRangeFilter">
    <param name="LevelMin" value="DEBUG" />
    <param name="LevelMax" value="WARN" />
  </filter>
</appender>
 The appenders listed either in the <root> tag or in the individual <logger> 
  tag are defined individually using the <appender> tag. The basic format 
  of the <appender> tag is defined above. It uses the appender name and 
  maps it to the class that defines that appender. Other important things to 
  see here are the tags inside of the <appender> element. The <param> 
  tag varies with different appenders. Here, to use the FileAppender, you need 
  a file name that you can define as a parameter. To complete the picture, a Layout 
  is defined inside of the <appender> tag. The layout is declared in its own 
  <layout> tag. The <layout> element defines the layout type (PatternLayout 
  in the example) and the parameters that are required by that layout 
  (as in the pattern string used by the PatternLayout class).
The Header and Footer tags provide the text to print before and after a logging 
  session. The details of configuring each appender are further described in the 
  documentation here, 
  where you can see individual appender section as examples.
The last thing is the <filter> tag in the Appender element. It defines 
  the filter to apply to a specific Appender. In this example, we are applying 
  the LevelRangeFilter, which extracts only those messages that fall between the 
  levels defined between the LevelMin and LevelMax parameters. Similarly, 
  other tags are defined, as appropriate. Multiple filters can be applied to an 
  appender, which then work in a pipeline in the sequence in which they are ordered. Other 
  filters and information on using them can be found in the log4net SDK 
  documents.
These are the necessary elements that we needed to initialize the log4net 
  framework for our application. Now that we have created the configuration file, 
  it's time to link to it from our application.
By default, every standalone executable assembly defines its own configuration 
  settings. The log4net framework uses the log4net.Config.DOMConfiguratorAttribute 
  on the assembly level to set the configuration file. There are three properties 
  for this attribute.
ConfigFile: The property is only used if we are defining the <log4net> 
    tag into our own configuration file.ConfigFileExtension: If we are using the application compiled 
    assembly with a different extension, then we need to define the extension here.Watch (Boolean): This is the property by which the log4net system 
    decides whether to watch the file for runtime changes or not. If the value 
    is true, then the FileSystemWatcher class is used to monitor the file for 
    change, rename, and delete notifications.  
[assembly:log4net.Config.DOMConfigurator(ConfigFile="filename", 
  ConfigFileExtension="ext",Watch=true/false)]
The log4net framework will consider the application's configuration file if 
  you do not define either the ConfigFile or ConfigFileExtension attribute. These attributes are mutually exclusive. We also need to keep it in 
  mind that the DOMConfigurator attribute is necessary and can be defined 
  as follows, with no parameters:
[assembly: log4net.Config.DOMConfigurator()]
There is another technique that saves you from having to use the attributes. It uses 
  the DOMConfigurator class inside of the code to load the configuration file provided   in the parameter. This method takes a FileInfo object instead of a file name. 
  This method has the same effect as loading the file through the attribute, as 
  shown previously.
log4net.Config.DOMConfigurator.Configure(
  new FileInfo("TestLogger.Exe.Config")); 
There is another method, ConfigureAndWatch(..), in the DOMConfigurator class, 
  to configure the framework to watch the file for any changes.
The above step concludes everything related to configuration. Next, the following two steps are required in our code to use the logger.
Log4net.ILog log = log4net.LogManager.GetLogger("logger-name");
IsXXXEnabled 
  Boolean variables before calling the methods to boost performance.
if (log.IsDebugEnabled) log.Debug("message");
if (log.IsInfoEnabled) log.Info("message);
//….
| 
 | 
log4net ProgrammaticallySometimes we are in the mood to code as quickly as possible without getting into configuration files. Normally, that happens when we are trying to test something. In that case, you have another way to do the configuration. All of the long configuration files that we saw in the previous section can be defined programmatically using a few lines of code. See the following code:
// using a FileAppender with a PatternLayout
log4net.Config.BasicConfigurator.Configure(
  new log4net.Appender.FileAppender(
     new log4net.Layout.PatternLayout("%d 
       [%t]%-5p %c [%x] <%X{auth}> - %m%n"),"testfile.log"));
// using a FileAppender with an XMLLayout
log4net.Config.BasicConfigurator.Configure( 
  new log4net.Appender.FileAppender( 
    new log4net.Layout.XMLLayout(),"testfile.xml"));
// using a ConsoleAppender with a PatternLayout
log4net.Config.BasicConfigurator.Configure( 
  new log4net.Appender.ConsoleAppender(
    new log4net.Layout.PatternLayout("%d 
      [%t] %-5p %c [%x] <%X{abc}> - %m%n")));
/
/ using a ConsoleAppender with a SimpleLayout
log4net.Config.BasicConfigurator.Configure(
  new log4net.Appender.ConsoleAppender(new 
    log4net.Layout.SimpleLayout()));
You can see that while it is easy to code here, you can't configure settings for individual loggers. All of the settings that are defined here are applied to the root logger.
The log4net.Config.BasicConfigurator class uses its static Configure method 
  to set an Appender object. The Appender constructor, in turn, requires the Layout 
  object. Other parameters are respective to the type of component you are 
  using.
You can also use BasicConfigurator.Configure() without any parameter to show 
  the output using ConsoleAppender with a specific PatternLayout, as follows:
Code
log4net.Config.BasicConfigurator.Configure();
Output
0 [1688] DEBUG log1 A B C - Test
20 [1688] INFO log1 A B C - Test
Now that the application is configured, you can write the logging code, as shown in the previous section.
One of the most noticeable features of log4net is its support for multithreaded 
  applications. This helps you in scenarios where your application is simultaneously 
  accessed by multiple clients. Therefore, to trace requests from different clients, 
  you need a mechanism to identify different clients in your logging framework. This 
  mechanism is provided in log4net through two different methods, Nested Diagnostic Context (NDC) and Mapped Diagnostic Context (MDC).
NDC uses a stack per thread to identify different clients. The stack's 
  Push() method is used to set any value that identifies the client. It is 
  the developer's responsibility to put in a unique value for each client. 
  To constrain NDC into a certain block of code, the developer can use the "using" 
  statement to make his task easier, because it automatically pops the respective 
  value from the stack.
using(log4net.NDC.Push("clientid")
{
  log.Info("message"); // output: "clientid – message"        
} // here the value is popped from the stack
NDC class can also be used without the using block.
log4net.NDC.Push("clientid"); // context started here
…. // put client aware log messages here
log4net.NDC.Pop(); // context ended here
The framework provides a special conversion term, "%x," to display 
  the NDC value on the stack using the PatternLayout.
<layout type="log4net.Layout.PatternLayout,log4net">
  <param name="ConversionPattern" value="%x" />
</layout>
If you push multiple values into the stack, then all of those values are concatenated in the output.
If you are using the XMLLayout class as the layout for your appender, then you 
  automatically have the NDC value as a CDATA section inside of the <log4net:NDC> 
  tag.
<log4net:NDC><![CDATA[A B C]]></log4net:NDC>Instead of using a stack, the MDC class uses a map to store individual user 
  information. It could not be used inside of the using block; therefore, we have to 
  use it in a Get()/Set() combination to manipulate the map values. Values 
  can be deleted from the map using the Remove() method. Similar to NDC, MDC also 
  works on a per-thread model, and requires the conversion term inside of
  the pattern string, if you are using the PatternLayout. For MDC, the term is  "%X" 
  (capital X) with the key concatenated to the character in curly braces. In the 
  following example, %X{clientid} is replaced with the value for the key clientid.
<layout type="log4net.Layout.PatternLayout">
  <param name="ConversionPattern" 
         value="%X{clientid}" 
  />
</layout>
Using log4net with ASP.NET is similar to the other applications. 
  Here, you can put the configuration attributes inside of the global.asax file, as well 
  as in any of the WebForms files. Putting it in global.asax is far easier 
  to remember. 
log4netThere is a lot left to explore in the log4net framework. New features are 
  also integrating into the framework quite frequently. Once you get started using 
  the information provided in this article, the next step would be to experiment 
  with the samples provided with the sources. In order to follow the updates on 
  the framework, I'll try to use the forum to post any changes. I would like to 
  acknowledge Nicko Cadell from www.Neoworks.com 
  for reviewing this article and helping me with some technical details.
log4net – log4net.sourceforge.netlog4net with WebServices"log4net manual: log4net.sourceforge.net/release/latest/doc/manual/introduction.htmlReturn to ONDotnet.com
Copyright © 2004 O'Reilly Media, Inc.