Contents
Frameworks
Loggers and Appenders
Configuration
Nested Diagnostic Contexts
Mapped Diagnostic Contexts
Plugins
Performance
Logging Domains
Logging Event Flow
More Information
Overview
This document is based on Short introduction to log4j by Ceki Gülcü.
The log4net framework is based on log4j, see
http://jakarta.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 Software License version 1.1, 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 utilise 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:
- Microsoft .Net Framework 1.0 (1.0.3705)
- Microsoft .Net Framework 1.1 (1.1.4322)
- Microsoft .Net Compact Framework 1.0 (1.0.5000)
- Mono 0.25
- Microsoft Shared Source CLI 1.0
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 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:
namespace log4net { public class LogManager { public static ILog GetLogger(string name); public static ILog GetLogger(Type type); } }
The GetLogger methods that takes a Type parameter uses the fully qualified type name as the name of the logger to retrieve.
These GetLogger methods return an ILog interface. That is the representation of the Logger passed back to the developer. The ILog interface is defined below:
namespace log4net { public interface ILog { void Debug(object message); void Info(object message); void Warn(object message); void Error(object message); void Fatal(object message); void Debug(object message, Exception t); void Info(object message, Exception t); void Warn(object message, Exception t); void Error(object message, Exception t); void Fatal(object message, Exception t); bool IsDebugEnabled { get; } bool IsInfoEnabled { get; } bool IsWarnEnabled { get; } bool IsErrorEnabled { get; } bool IsFatalEnabled { get; } } }
Loggers may be assigned levels. Levels are instances of the log4net.spi.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.
For example, in:
ILog x = LogManager.GetLogger("wombat"); ILog y = LogManager.GetLogger("wombat");
x and y refer to exactly the 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.
The following appenders are defined in the log4net package:
Type | Description |
---|---|
log4net.Appender.ADONetAppender | Writes logging events to a database using either prepared statements or stored procedures. |
log4net.Appender.ASPNetTraceAppender | Writes logging events to the ASP trace context. These can then be rendered at the end of the ASP page or on the ASP trace page. |
log4net.Appender.BufferingForwardingAppender | Buffers logging events before forwarding them to child appenders. |
log4net.Appender.ColoredConsoleAppender | 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. |
log4net.Appender.ConsoleAppender | Writes logging events to the application's Console. The events may go to either the standard our stream or the standard error stream. |
log4net.Appender.EventLogAppender | Writes logging events to the Windows Event Log. |
log4net.Appender.FileAppender | Writes logging events to a file in the file system. |
log4net.Appender.ForwardingAppender | Forwards logging events to child appenders. |
log4net.Appender.MemoryAppender | Stores logging events in an in memory buffer. |
log4net.Appender.NetSendAppender | Writes logging events to the Windows Messenger service. These messages are displayed in a dialog on a users terminal. |
log4net.Appender.OutputDebugStringAppender | 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. |
log4net.Appender.RemotingAppender | Writes logging events to a remoting sink using .NET remoting. |
log4net.Appender.RollingFileAppender | Writes logging events to a file in the file system. The RollingFileAppender can be configured to log to multiple files based upon date or file size constraints. |
log4net.Appender.SMTPAppender | Sends logging events to an email address. |
log4net.Appender.SmtpPickupDirAppender | Writes SMTP messages as files into a pickup directory. These files can then be read and sent by an SMTP agent such as the IIS SMTP agent. |
log4net.Appender.TraceAppender | Writes logging events to the .NET trace system. |
log4net.Appender.UdpAppender | 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 behaviour 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 IFilter.
The following filters are defined in the log4net package:
Type | Description |
---|---|
log4net.Filter.DenyAllFilter | Drops all logging events. |
log4net.Filter.LevelMatchFilter | An exact match to the event's level. |
log4net.Filter.MDCFilter | Matches a substring from a specific MDC value. |
log4net.Filter.NDCFilter | Matches a substring from the event's NDC. |
log4net.Filter.LevelRangeFilter | Matches against a range of levels. |
log4net.Filter.StringMatchFilter | Matches a substring from the event's message. |
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.
For example, the PatternLayout with the conversion pattern "%r [%t] %-5p %c - %m%n" will output something akin to:
176 [main] INFO Com.Foo.Bar - Located nearest gas station.
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.
The following layouts are included in the log4net package:
Type | Description |
---|---|
log4net.Layout.ExceptionLayout | Renders the exception text from the logging event. |
log4net.Layout.PatternLayout | Formats the logging event according to a flexible set of formatting flags. |
log4net.Layout.RawTimeStampLayout | Extracts the timestamp from the logging event. |
log4net.Layout.SimpleLayout | Formats the logging event very simply: [level] - [message] |
log4net.Layout.XMLLayout | Formats the logging event as an XML element. |
log4net.Layout.XMLLayoutSchemaLog4j | 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.
Configuration
Inserting log requests into the application code requires a fair amount of planning and effort. Observation shows that approximately 4 percent of code is dedicated to logging. Consequently, even moderately sized applications will have thousands of logging statements embedded within their code. Given their number, it becomes imperative to manage these log statements without the need to modify them manually.
The log4net environment is fully configurable programmatically. However, it is far more flexible to configure log4net using configuration files. Currently, configuration files are written in XML.
Let us give a taste of how this is done with the help of an imaginary application MyApp that uses log4net.
using Com.Foo; // Import log4net classes. using log4net; using log4net.Config; public class MyApp { // Define a static logger variable so that it references the // Logger instance named "MyApp". private static readonly ILog log = LogManager.GetLogger(typeof(MyApp)); static void Main(string[] args) { // Set up a simple configuration that logs on the console. BasicConfigurator.Configure(); log.Info("Entering application."); Bar bar = new Bar(); bar.DoIt(); log.Info("Exiting application."); } }
MyApp begins by importing log4net related classes. It then defines a static logger variable with the name MyApp which happens to be the fully qualified name of the class.
MyApp uses the following Bar class:
// Import log4net classes. using log4net; namespace Com.Foo { public class Bar { private static readonly ILog log = LogManager.GetLogger(typeof(Bar)); public void DoIt() { log.Debug("Did it again!"); } } }
The invocation of the BasicConfigurator.Configure() method creates a rather simple log4net setup. This method is hardwired to add to the root logger a ConsoleAppender. The output will be formatted using a PatternLayout set to the pattern "%-4r [%t] %-5p %c %x - %m%n".
Note that by default, the root logger is assigned to Level.DEBUG.
The output of MyApp is:
0 [main] INFO MyApp - Entering application. 36 [main] DEBUG Com.Foo.Bar - Did it again! 51 [main] INFO MyApp - Exiting application.
As a side note, let me mention that in log4net child loggers link only to their existing ancestors. In particular, the logger named Com.Foo.Bar is linked directly to the root logger, thereby circumventing the unused Com or Com.Foo loggers. This significantly increases performance and reduces log4net's memory footprint.
The MyApp class configures log4net by invoking BasicConfigurator.Configure() method. Other classes only need to import the log4net namespace, retrieve the loggers they wish to use, and log away.
The previous example always outputs the same log information. Fortunately, it is easy to modify MyApp so that the log output can be controlled at run-time. Here is a slightly modified version.
using Com.Foo; // Import log4net classes. using log4net; using log4net.Config; public class MyApp { private static readonly ILog log = LogManager.GetLogger(typeof(MyApp)); static void Main(string[] args) { // BasicConfigurator replaced with DOMConfigurator. DOMConfigurator.Configure(new System.IO.FileInfo(args[0])); log.Info("Entering application."); Bar bar = new Bar(); bar.DoIt(); log.Info("Exiting application."); } }
This version of MyApp instructs the DOMConfigurator to parse a configuration file and set up logging accordingly. The path to the configuration file is specified on the command line.
Here is a sample configuration file that results in exactly same output as the previous BasicConfigurator based example.
<log4net> <!-- A1 is set to be a ConsoleAppender --> <appender name="A1" type="log4net.Appender.ConsoleAppender"> <!-- A1 uses PatternLayout --> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%-4r [%t] %-5p %c %x - %m%n" /> </layout> </appender> <!-- Set root logger level to DEBUG and its only appender to A1 --> <root> <level value="DEBUG" /> <appender-ref ref="A1" /> </root> </log4net>
Suppose we are no longer interested in seeing the output of any component belonging to the Com.Foo package. The following configuration file shows one possible way of achieving this.
<log4net> <!-- A1 is set to be a ConsoleAppender --> <appender name="A1" type="log4net.Appender.ConsoleAppender"> <!-- A1 uses PatternLayout --> <layout type="log4net.Layout.PatternLayout"> <!-- Print the date in ISO 8601 format --> <conversionPattern value="%d [%t] %-5p %c %x - %m%n" /> </layout> </appender> <!-- Set root logger level to DEBUG and its only appender to A1 --> <root> <level value="DEBUG" /> <appender-ref ref="A1" /> </root> <!-- Print only messages of level WARN or above in the package Com.Foo --> <logger name="Com.Foo"> <level value="WARN" /> </logger> </log4net>
The output of MyApp configured with this file is shown below.
2000-09-07 14:07:41,508 [main] INFO MyApp - Entering application. 2000-09-07 14:07:41,529 [main] INFO MyApp - Exiting application.
As the logger Com.Foo.Bar does not have an assigned level, it inherits its level from Com.Foo, which was set to WARN in the configuration file. The log statement from the Bar.DoIt method has the level DEBUG, lower than the logger level WARN. Consequently, DoIt() method's log request is suppressed.
Here is another configuration file that uses multiple appenders.
<log4net> <appender name="Console" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout"> <!-- Pattern to output the caller's file name and line number --> <conversionPattern value="%5p [%t] (%F:%L) - %m%n" /> </layout> </appender> <appender name="RollingFile" type="log4net.Appender.RollingFileAppender"> <file value="example.log" /> <appendToFile value="true" /> <maximumFileSize value="100KB" /> <maxSizeRollBackups value="2" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%p %t %c - %m%n" /> </layout> </appender> <root> <level value="DEBUG" /> <appender-ref ref="Console" /> <appender-ref ref="RollingFile" /> </root> </log4net>
Calling the enhanced MyApp with the this configuration file will output the following on the console.
INFO [main] (MyApp.cs:16) - Entering application. DEBUG [main] (Bar.cs:12) - Doing it again! INFO [main] (MyApp.cs:19) - Exiting application.
In addition, as the root logger has been allocated a second appender, output will also be directed to the example.log file. This file will be rolled over when it reaches 100KB. When roll-over occurs, the old version of example.log is automatically moved to example.log.1.
Note that to obtain these different logging behaviours we did not need to recompile code. We could just as easily have logged to an email address, redirected all Com.Foo output to an NT Event logger, or forwarded logging events to a remote log4net server, which would log according to local server policy.
For more examples of configuring appenders using the DOMConfigurator see the Example Appender Configuration document.
Configuration Attributes
The log4net configuration can be configured using assembly-level attributes rather than specified programmatically.
-
DOMConfiguratorAttribute
The log4net.Config.DOMConfiguratorAttribute Allows the DOMConfigurator to be configured using the following properties:
-
ConfigFile
If specified, this is the filename of the configuration file to use with the DOMConfigurator. This file path is relative to the application base directory (AppDomain.CurrentDomain.BaseDirectory).
This property cannot be used in conjunction with the ConfigFileExtension property.
-
ConfigFileExtension
If specified, this is the extension for the configuration file. The assembly file name is used as the base name with the this extension appended. For example if the assembly is loaded from the a file TestApp.exe and the ConfigFileExtension property is set to log4net then the configuration file name is TestApp.exe.log4net. This is equivalent to setting the ConfigFile property to TestApp.exe.log4net.
The path to the configuration file is build by using the application base directory (AppDomain.CurrentDomain.BaseDirectory), the assembly file name and the configuration file extension.
This property cannot be used in conjunction with the ConfigFile property.
-
Watch
If this flag is specified and set to true then the framework will watch the configuration file and will reload the config each time the file is modified.
If neither of the ConfigFile or ConfigFileExtension properties are specified, the application configuration file (e.g. TestApp.exe.config) will be used as the log4net configuration file.
Example usage:
// Configure log4net using the .config file [assembly: log4net.Config.DOMConfigurator(Watch=true)] // This will cause log4net to look for a configuration file // called TestApp.exe.config in the application base // directory (i.e. the directory containing TestApp.exe) // The config file will be watched for changes.
// Configure log4net using the .log4net file [assembly: log4net.Config.DOMConfigurator(ConfigFileExtension="log4net",Watch=true)] // This will cause log4net to look for a configuration file // called TestApp.exe.log4net in the application base // directory (i.e. the directory containing TestApp.exe) // The config file will be watched for changes.
This attribute may only be used once per assembly.
-
Using attributes can be a clearer method for defining where the application's configuration will be loaded from. However it is worth noting that attributes are purely passive. They are information only. Therefore if you use configuration attributes you must invoke log4net to allow it to read the attributes. A simple call to LogManager.GetLogger will cause the attributes on the calling assembly to be read and processed. Therefore it is imperative to make a logging call as early as possible during the application start-up, and certainly before any external assemblies have been loaded and invoked.
Configuration Files
Typically the log4net configuration is specified using a file. This file can be read in one of two ways:
- Using the .NET System.Configuration API
- Reading the file contents directly
.config Files
The System.Configuration API is only available if the configuration data is in the application's config file; the file named MyApp.exe.config or Web.config. Because the System.Configuration API does not support reloading of the config file the configuration settings cannot be watched using the log4net.Config.DOMConfigurator.ConfigureAndWatch methods. The main advantage of using the System.Configuration APIs to read the configuration data is that it requires less permissions than accessing the configuration file directly.
The only way to configure an application using the System.Configuration APIs is to call the log4net.Config.DOMConfigurator.Configure() method or the log4net.Config.DOMConfigurator.Configure(ILoggerRepository) method.
In order to embed the configuration data in the .config file the section name must be identified to the .NET config file parser using a configSections element. The section must specify the log4net.Config.Log4NetConfigurationSectionHandler that will be used to parse the config section. This type must be fully assembly qualified because it is being loaded by the .NET config file parser not by log4net. The correct assembly name for the log4net assembly must be specified. The following is a simple example configuration file that specifies the correct section handler to use for the log4net section.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> </configSections> <log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" > <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" /> </layout> </appender> <root> <level value="INFO" /> <appender-ref ref="ConsoleAppender" /> </root> </log4net> </configuration>
In the above example the log4net assembly is specified. This assembly must be located where the .NET runtime can find it. For example it could be located in the same directory as the application. If the log4net assembly is stored in the GAC then the fully qualified assembly name must be specified including the culture, version and public key.
When using the .config file to specify the configuration the section name and XML element name must be log4net.
Reading Files Directly
The DOMConfigurator can directly read any XML file and use it to configure log4net. This includes the application's .config file; the file named MyApp.exe.config or Web.config. The only reason not to read the configuration file directly is if the application does not have sufficient permissions to read the file, then the configuration must be loaded using the .NET configuration APIs (see above).
The file to read the configuration from can be specified using any of the log4net.Config.DOMConfigurator methods that accept a System.IO.FileInfo object. Because the file system can be monitored for file change notifications the ConfigureAndWatch methods can be used to monitor the configuration file for modifications and automatically reconfigure log4net.
Additionally the log4net.Config.DOMConfiguratorAttribute can be used to specify the file to read the configuration from.
The configuration is read from the log4net element in the file. Only one log4net element can be specified in the file but it may be located anywhere in the XML hierarchy. For example it may be the root element:
<?xml version="1.0" encoding="utf-8" ?> <log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" > <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" /> </layout> </appender> <root> <level value="INFO" /> <appender-ref ref="ConsoleAppender" /> </root> </log4net>
Or it may be nested within other elements:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="log4net" type="System.Configuration.IgnoreSectionHandler" /> </configSections> <log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" > <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" /> </layout> </appender> <root> <level value="INFO" /> <appender-ref ref="ConsoleAppender" /> </root> </log4net> </configuration>
The above example shows how the configuration data can be embedded inside a .config file even though the file is being read directly by log4net. An important note is that the .NET config file parser will throw an exception if it finds an element that has not been registered using the configSections element. Therefore in the above example the log4net section name is registered, but the type specified to handle the section is System.Configuration.IgnoreSectionHandler. This is a built-in class that indicates that another method for reading the config section will be employed.
Configuration Syntax
log4net includes a configuration reader that parses an XML DOM, the log4net.Config.DOMConfigurator. This section defines the syntax accepted by the configurator.
This is an example of a valid XML configuration. The root element must be <log4net>. Note that this does not mean that this element cannot be embedded in another XML document. See the section above on Configuration Files for more information on how to embed the DOMConfigurator XML in a configuration file.
<log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" > <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" /> </layout> </appender> <root> <level value="INFO" /> <appender-ref ref="ConsoleAppender" /> </root> </log4net>
The <log4net> element supports the following attributes:
Attribute | Description |
---|---|
debug | Optional attribute. Value must be either true or false. The default value is false. Set this attribute to true to enable internal log4net debugging for this configuration. |
update | Optional attribute. Value must be either Merge or Overwrite. The default value is Merge. Set this attribute to Overwrite to reset the configuration of the repository being configured before applying this configuration. |
threshold | Optional attribute. Value must be the name of a level registered on the repository. The default value is ALL. Set this attribute to limit the messages that are logged across the whole repository, regardless of the logger that the message is logged to. |
The <log4net> element supports the following child elements:
Element | Description |
---|---|
appender | Zero or more elements allowed. Defines an appender. |
logger | Zero or more elements allowed. Defines the configuration of a logger. |
renderer | Zero or more elements allowed. Defines an object renderer. |
root | Optional element, maximum of one allowed. Defines the configuration of the root logger. |
param | Zero or more elements allowed. Repository specific parameters |
Appenders
Appenders may only be defined as child elements of the <log4net> element. Each appender must be uniquely named. The implementing type for the appender must be specified.
This example shows an appender of type log4net.Appender.ConsoleAppender being defined. The appender will be known as ConsoleAppender.
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" > <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" /> </layout> </appender>
The <appender> element supports the following attributes:
Attribute | Description |
---|---|
name | Required attribute. Value must be a string name for this appender. The name must be unique among all the appenders defined in this configuration file. This name is used by the <appender-ref> element of a Logger to reference an appender. |
type | Required attribute. Value must be the type name for this appender. If the appender is not defined in the log4net assembly this type name must be fully assembly qualified. |
The <appender> element supports the following child elements:
Element | Description |
---|---|
appender-ref | Zero or more elements allowed. Allows the appender to reference other appenders. Not supported by all appenders. |
filter | Zero or more elements allowed. Defines the filters used by this appender. |
layout | Optional element, maximum of one allowed. Defines the layout used by this appender. |
param | Zero or more elements allowed. Appender specific parameters. |
For examples of configuring appenders see the Example Appender Configuration document.
Filters
Filters elements may only be defined as children of <appender> elements.
The <filter> element supports the following attributes:
Attribute | Description |
---|---|
type | Required attribute. Value must be the type name for this filter. If the filter is not defined in the log4net assembly this type name must be fully assembly qualified. |
The <filter> element supports the following child elements:
Element | Description |
---|---|
param | Zero or more elements allowed. Filter specific parameters. |
Filters form a chain that the event has to pass through. Any filter along the way can accept the event and stop processing, deny the event and stop processing, or allow the event on to the next filter. If the event gets to the end of the filter chain without being denied it is implicitly accepted and will be logged.
<filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="INFO" /> <param name="LevelMax" value="FATAL" /> </filter>
This filter will deny events that have a level that is lower than INFO or higher than FATAL. All events between INFO and FATAL will be logged.
If we want to only allow messages through that have a specific substring (e.g. 'database') then we need to specify the following filters:
<filter type="log4net.Filter.StringMatchFilter"> <param name="StringToMatch" value="database" /> </filter> <filter type="log4net.Filter.DenyAllFilter" />
The first filter will look for the substring 'database' in the message text of the event. If the text is found the filter will accept the message and filter processing will stop, the message will be logged. If the substring is not found the event will be passed to the next filter to process. If there is no next filter the event would be implicitly accepted and would be logged. But because we don't want the non matching events to be logged we need to use a log4net.Filter.DenyAllFilter that will just deny all events that reach it. This filter is only useful at the end of the filter chain.
If we want to allow events that have either 'database' or 'ldap' in the message text we can use the following filters:
<filter type="log4net.Filter.StringMatchFilter"> <param name="StringToMatch" value="database"/> </filter> <filter type="log4net.Filter.StringMatchFilter"> <param name="StringToMatch" value="ldap"/> </filter> <filter type="log4net.Filter.DenyAllFilter" />
Layouts
Layout elements may only be defined as children of <appender> elements.
The <layout> element supports the following attributes:
Attribute | Description |
---|---|
type | Required attribute. Value must be the type name for this layout. If the layout is not defined in the log4net assembly this type name must be fully assembly qualified. |
The <layout> element supports the following child elements:
Element | Description |
---|---|
param | Zero or more elements allowed. Layout specific parameters. |
This example shows how to configure a layout that uses the log4net.Layout.PatternLayout.
<layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" /> </layout>
Root Logger
Only one root logger element may only be defined and it must be a child of <log4net> element. The root logger is the root of the logger hierarchy. All logger ultimately inherit from this logger.
An example root logger:
<root> <level value="INFO" /> <appender-ref ref="ConsoleAppender" /> </root>
The <root> element supports no attributes.
The <root> element supports the following child elements:
Element | Description |
---|---|
appender-ref | Zero or more elements allowed. Allows the logger to reference appenders by name. |
level | Optional element, maximum of one allowed. Defines the logging level for this logger. This logger will only accept event that are at this level or above. |
param | Zero or more elements allowed. Logger specific parameters. |
Loggers
Logger elements may only be defined as children of the <log4net> element.
An example logger:
<logger name="LoggerName"> <level value="DEBUG" /> <appender-ref ref="ConsoleAppender" /> </logger>
The <logger> element supports the following attributes.
Attribute | Description |
---|---|
name | Required attribute. Value must be the name of the logger. |
additivity | Required attribute. Value must be either true or false. The default value is true. Set this attribute to false to prevent this logger from inheriting the appenders defined on parent loggers. |
The <logger> element supports the following child elements:
Element | Description |
---|---|
appender-ref | Zero or more elements allowed. Allows the logger to reference appenders by name. |
level | Optional element, maximum of one allowed. Defines the logging level for this logger. This logger will only accept event that are at this level or above. |
param | Zero or more elements allowed. Logger specific parameters. |
Renderers
Renderer elements may only be defined as children of the <log4net> element.
An example renderer:
<renderer renderingClass="MyClass.MyRenderer" renderedClass="MyClass.MyFunkyObject" />
The <renderer> element supports the following attributes.
Attribute | Description |
---|---|
renderingClass | Required attribute. Value must be the type name for this renderer. If the type is not defined in the log4net assembly this type name must be fully assembly qualified. This is the type of the object that will take responsibility for rendering the renderedClass. |
renderedClass | Required attribute. Value must be the type name for the target type for this renderer. If the type is not defined in the log4net assembly this type name must be fully assembly qualified. This is the name of the type that this renderer will render. |
The <renderer> element supports no child elements.
Parameters
Parameter elements may be children of many elements. See the specific elements above for details.
An example param:
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
The <param> element supports the following attributes.
Attribute | Description |
---|---|
name | Required attribute. Value must be the name of the parameter to set on the parent object. |
value | Optional attribute. One of value or type attributes must be specified. The value of this attribute is a string that can be converted to the value of the parameter. |
type | Optional attribute. One of value or type attributes must be specified. The value of this attribute is a type name to create and set as the value of the parameter. If the type is not defined in the log4net assembly this type name must be fully assembly qualified. |
The <param> element supports the following child elements:
Element | Description |
---|---|
param | Zero or more elements allowed. Parameter specific parameters. |
An example param that uses nested param elements:
<param name="evaluator" type="log4net.spi.LevelEvaluator"> <param name="Threshold" value="WARN"/> <param>
Compact Parameter Syntax
Parameters may also be specified using the parameter name as the element name rather than using the param element and name attribute.
For example a param:
<param name="evaluator" type="log4net.spi.LevelEvaluator"> <param name="Threshold" value="WARN"/> <param>
may be written as:
<evaluator type="log4net.spi.LevelEvaluator"> <threshold value="WARN"/> <evaluator>
Nested Diagnostic Contexts
Most real-world systems have to deal with multiple clients simultaneously. In a typical multithreaded implementation of such a system, different threads will handle different clients. Logging is especially well suited to trace and debug complex distributed applications. A common approach to differentiate the logging output of one client from another is to instantiate a new separate logger for each client. This promotes the proliferation of loggers and increases the management overhead of logging.
A lighter technique is to uniquely stamp each log request initiated from the same client interaction.
To uniquely stamp each request, the user pushes contextual information into the NDC, the abbreviation of Nested Diagnostic Context. The NDC class is shown below.
namespace log4net { public class NDC { /// Removes the top context from the stack. public static string Pop(); /// Pushes a new context message. public static IDisposable Push(string message); /// Gets the current context information. internal static string Get(); } }
The NDC is managed per thread as a stack of contextual information. Note that all methods of the log4net.NDC class are static. Assuming that NDC printing is turned on, every time a log request is made, the appropriate log4net component will include the entire NDC stack for the current thread in the log output. This is done without the intervention of the user, who is responsible only for placing the correct information in the NDC by using the Push and Pop methods at a few well-defined points in the code. In contrast, the per-client logger approach commands extensive changes in the code.
The NDC.Push method returns an IDisposable object that can be used to clean up the NDC stack. This means that the user does not have to manually match up a Pop call for each Push call. The using syntax can be used to modify the NDC only for a specific block of code. For example:
NDC.Push("context"); log.Info("Message"); NDC.Pop();
Is equivalent to:
using(NDC.Push("context")) { log.Info("Message"); }
The using syntax is recommended because it removes some work load from the developer and reduces errors in matching up the Push and Pop calls, especially when exceptions can occur.
To illustrate this point, let us take the example of a web service delivering content to numerous clients. The web service can build the NDC at the very beginning of the request before executing other code. The contextual information can be the client's host name and other information inherent to the request, typically information contained in cookies. Hence, even if the web service is serving multiple clients simultaneously, the logs initiated by the same code, i.e. belonging to the same logger, can still be distinguished because each client request will have a different NDC stack. Contrast this with the complexity of passing a freshly instantiated logger to all code exercised during the client's request.
Nevertheless, some sophisticated applications, such as virtual hosting web servers, must log differently depending on the virtual host context and also depending on the software component issuing the request. Log4net supports multiple hierarchy trees. This allows each virtual host to possess its own copy of the logger hierarchy. Configuring multiple logger hierarchies is beyond the scope of this document.
Mapped Diagnostic Contexts
The MDC (Mapped Diagnostic Context) is used to set thread specific named properties. These properties can be rendered by the PatternLayout.
The MDC class is shown below.
namespace log4net { public class MDC { /// Gets the context identified by the key parameter. public static string Get(string key); /// Puts a context value (the val parameter) as identified with /// the key parameter into the current thread's context map. public static void Set(string key, string value); /// Removes the key value mapping for the key specified. public static void Remove(string key); } }
Plugins
Plugins are additional modular components that are attached to a logger repository.
Plugins are stored in the PluginMap of an ILoggerRepository. Plugins are attached to the repository by using the PluginMap.Add method.
The following plugins are included in the log4net package:
Type | Description |
---|---|
log4net.Plugin.RemoteLoggingServerPlugin | Creates a remote logging sink that can receive logging events from a RemotingAppender. |
-
RemoteLoggingServerPlugin
Creates a remote logging sink that can receive logging events from a RemotingAppender.
Creates a remoting logging sink. A single parameter must be passed to the constructor that specifies the sink URI. This is a name used to identify the logging sink object published via remoting and must be agreed with the client before communication can take place.
Example usage:
LogManager.GetLoggerRepository().PluginMap.Add(new RemoteLoggingServerPlugin("LoggingSink"));
Plugin Attributes
Plugins can be configured using the following assembly-level attributes:
-
PluginAttribute
Specifies a plugin type to create and attach to the default repository. This attribute does not allow plugins to be parameterised. The plugin class must have a public default constructor.
This attribute may be used as many times as necessary to attach plugins to the repository.
Performance
One of the often-cited arguments against logging is its computational cost. This is a legitimate concern as even moderately sized applications can generate thousands of log requests. Much effort was spent measuring and tweaking logging performance. Log4net claims to be fast and flexible: speed first, flexibility second.
The user should be aware of the following performance issues.
-
Logging performance when logging is turned off.
When logging is turned off entirely or just for a set of levels, the cost of a log request consists of a method invocation plus an integer comparison.
However, The method invocation involves the "hidden" cost of parameter construction.
For example, for some logger log, writing,
log.Debug("Entry number: " + i + " is " + entry[i].ToString());
incurs the cost of constructing the message parameter, i.e. converting both integer i and entry[i] to strings, and concatenating intermediate strings, regardless of whether the message will be logged or not. This cost of parameter construction can be quite high and it depends on the number and type of the parameters involved.
To avoid the parameter construction cost write:
if(log.IsDebugEnabled) { log.Debug("Entry number: " + i + " is " + entry[i].ToString()); }
This will not incur the cost of parameter construction if debugging is disabled. On the other hand, if the logger is debug-enabled, it will incur twice the cost of evaluating whether the logger is enabled or not: once in IsDebugEnabled and once in Debug. This is an insignificant overhead because evaluating a logger takes about 1% of the time it takes to actually log.
Certain users resort to pre-processing or compile-time techniques to compile out all log statements. This leads to perfect performance efficiency with respect to logging. However, since the resulting application binary does not contain any log statements, logging cannot be turned on for that binary. In many people's opinion this is a disproportionate price to pay in exchange for a small performance gain.
-
The performance of deciding whether to log or not to log when logging is
turned on.
This is essentially the performance of walking the logger hierarchy. When logging is turned on, log4net still needs to compare the level of the log request with the level of the request logger. However, loggers may not have an assigned level; they can inherit them from the logger hierarchy. Thus, before inheriting a level, the logger may need to search its ancestors.
There has been a serious effort to make this hierarchy walk to be as fast as possible. For example, child loggers link only to their existing ancestors. In the BasicConfigurator example shown earlier, the logger named Com.Foo.Bar is linked directly to the root logger, thereby circumventing the nonexistent Com or Com.Foo loggers. This significantly improves the speed of the walk, especially in "sparse" hierarchies.
The typical cost of walking the hierarchy is typically 3 times slower than when logging is turned off entirely.
-
Actually outputting log messages
This is the cost of formatting the log output and sending it to its target destination. Here again, a serious effort was made to make layouts (formatters) perform as quickly as possible. The same is true for appenders.
Although log4net has many features, its first design goal was speed. Some log4net components have been rewritten many times to improve performance. Nevertheless, contributors frequently come up with new optimizations. You should be pleased to know that when configured with the SimpleLayout performance tests have shown log4net to log within an order of magnitude of System.Console.WriteLine.
Logging Domains
Domain Attributes
Logging domains are considered advanced functionality. The default behaviour should be sufficient for most users.
Log4net supports logging domains. A domain is uniquely named. Each domain links to a single repository (ILoggerRepository). Multiple domains can link to the same repository.
By default there is a single logging domain per process (or AppDomain). This extends across all assemblies loaded into the process and allows them to all share a single configuration. The configuration of the domain's repository only needs to be done once, typically in the entry point to the application, either programmatically or using a configuration attribute.
Named logging domains can be created using the LogManager.CreateDomain method. The repository for a domain can be retrieved using the LogManager.GetLoggerRepository method. A repository created in this way will need to be configured programmatically.
An assembly may choose to utilise a named logging domain rather than the default domain. This completely separates the logging for the assembly from the rest of the application. This can be very useful to component developers that wish to use log4net for their components but do not want to require that all the applications that use their component are aware of log4net. It also means that their debugging configuration is separated from the applications configuration. The assembly should specify the DomainAttribute to set its logging domain name.
The log4net logging domains can be configured using the following assembly-level attributes:
-
AliasDomainAttribute
Specifies a domain to alias to this assembly's repository.
An assembly's logger repository is defined by its DomainAttribute, however this can be overridden by an assembly loaded before the target assembly.
An assembly can alias another assembly's domain to its repository by specifying this attribute with the name of the target domain.
This attribute may be used as many times as necessary to alias all the required domains.
-
DomainAttribute
Specifies the logging domain for the assembly.
Assemblies are mapped to logging domains. Each domain has its own logging repository. This attribute controls the configuration of the domain. The Name property specifies the name of the domain for this assembly. The RepositoryType property specifies the type of the repository object to create for the domain. If this attribute is not specified and a Name is not specified then the assembly will be part of the default shared logging domain.
This attribute may only be used once per assembly.
Logging Event Flow
The following is the series of steps and checks that a messages goes through while being logged. For the purposes of this example we will document an INFO level message being logged on logger ConsoleApp.LoggingExample. This logger is configured to use the log4net.Appender.ConsoleAppender. The repository used in this example is a log4net.Repository.Hierarchy object.
-
The user logs a message using the ILog.Info method on the logger obtained using a call to log4net.LogManager.GetLogger("ConsoleApp.LoggingExample"). For example: log4net.LogManager.GetLogger("ConsoleApp.LoggingExample").Info("Application Start"); The ILog interface is actually an extension to log4net that provides level specific logging methods (i.e. Debug, Info, Warn, Error, and Fatal).
-
The message is then logged through to the ILogger.Log method on the appropriate log4net.Repository.Hierarchy.Logger object. The ILogger.Log method takes the Level to log at as a parameter and therefore works for all levels.
-
The repository threshold level is compared to the message level to determine if the message can be logged. If the message level is below the threshold level the message is not logged. In this case the repository is a log4net.Repository.Hierarchy object.
-
The Logger level is compared to the message level to determine if the message can be logged. Note that the Logger level is inherited from a parent Logger if not specified explicitly for this Logger. If the message level is below the Logger level the message is not logged.
-
A LoggingEvent instance is created to encapsulate the message being logged.
-
The list of appenders for the Logger is built. This includes appenders attached to parent Loggers except where excluded by the Logger.Additivity property.
-
The LoggingEvent object is passed to the IAppender.DoAppend method for each appender.
For Each Appender that the LoggingEvent is delivered to the following actions take place:
-
The appender threshold level is compared to the message level to determine if the message can be logged. If the message level is below the threshold level the message is not logged.
-
If the appender has a filter chain the LoggingEvent is passed down the filter chain which can decide if the message can be logged or not.
-
Next an appender specific check is performed. Usually this check will verify that all the required properties are set for the appender (e.g. a Layout is set if required).
-
The LoggingEvent is passed to the appender specific Append method. What happens now is specific to the appender.
The following actions take place in the ConsoleAppender.Append method:
-
The ConsoleAppender uses a Layout to format the message as a string for display.
-
The Layout uses the LoggingEvent.RenderedMessage property to get the string for the message object. This uses the registered IObjectRenderer for the type of the message object.
-
The message text is displayed on the console using the Console.WriteLine method.
More Information
For more information on frameworks that log4net supports see the Framework Support document.
For answers to common questions see the Frequently Asked Questions document.
For more information on log4net see http://www.log4net.org.
The log4net project is hosted on sourceforge. The project page is at http://www.sourceforge.net/projects/log4net.
Discussion on log4net and logging in general are held on the log4net-users mailing list. To subscribe to this list visit: http://lists.sourceforge.net/lists/listinfo/log4net-users.
The log4net framework is based on log4j see http://jakarta.apache.org/log4j for more information on log4j.