Logging, the .NET Framework, and why I used Log4NET

So recently I’ve had a need to do quite a lot of logging. Moreover, I needed the logging I was doing to be very flexible. I’m kind of new to .NET, and so I found myself learning about the ‘Debugging’ and ‘Tracing’ in it. (Incidently, WTF – they couldn’t call it ‘Logging’? Presumably, this is ‘cos Java has a ‘Logging’ api).

Long story short, .NET’s built in logging wasn’t bad, but the Log4NET project proved a lot better.

Within the .NET Framework

.NET has some classes for doing this – Debug and Trace. They work, and you can configure trace listeners and such. However, writing code to send a debug message only if the logging level is detailed enough is, well, clumsy.

Debug and Trace are pretty much equivalent, except that Debug is not compiled into ‘Release’ builds. They have methods like the following:

Debug.WriteLine ( string )
Debug.WriteLineIf ( bool condition, string )
Debug.Assert ( bool condition, string message)

In addition, Trace has things like:

Trace.TraceInformation ( string )
Trace.TraceWarning ( string )
Trace.TraceError ( string )

It’s worth noting that none of these Trace methods have an ‘if’ equivalent to ‘WriteLineIf’, and that the Debug class can’t output different ‘levels’ of debug message.

Trace also works with a TraceSwitch class. This allows you to switch levels of logging used, and can be configured through the application config. E.g., this adds a listener to output to the Console:

<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<add name="configConsoleListener" type="System.Diagnostics.ConsoleTraceListener" />
</listeners>
</trace>
</system.diagnostics>
</configuration>

Similarly, TraceSwitches logging levels can be set in this file. The problem with them is, though, that you have to make sure you test the logging level before you call the Trace.TraceInformation style methods. For example:

TraceSwitch sw = new TraceSwitch("General", "Whole App");
sw.Level = TraceLevel.Error;
if (sw.TraceError )
Trace.WriteLine("Error1");

if (sw.TraceWarning)
Trace.WriteLine("Warning1");

if (sw.TraceInfo )
Trace.WriteLine("Info1");

Trace.TraceError("Error2");
Trace.TraceWarning("Warning2");
Trace.TraceInformation("Info2");

Produces:

Error1
TraceTest.vshost.exe Error: 0 : Error2
TraceTest.vshost.exe Warning: 0 : Warning2
TraceTest.vshost.exe Information: 0 : Info2

In other words, Trace.TraceXXX methods don’t obey a global logging level.

The final component in the .NET logging are TraceListeners. These listen to Trace or Debug messages, and log them somewhere. Examples that are built in include the Console, a text file, an XML file, and the Windows Event Log. You can have many TraceListeners, but I didn’t see a way of having different listeners recording different levels of information (e.g. Errors to the event log, but all messages to an XML file).

I wanted to log to the Windows Event Log. Specifically, I wanted to create Warning and Error messages in the event log, but it turns out that the .NET EventLogTraceListener only ever creates items as ‘Information’ messages (i.e. messages with the wrong icon). That kind of sucked.

Log4NET Logging Framework

So I looked at Log4NET. This is an Apache project, which was originally to build a Java Logging API. Java now has a Logging API, which works in a suspiciously similar way. Anyway, .NET also lacked good logging, so they ported the project to .NET.

Log4NET still has a number of nice features – it’s much more configurable, supports having different levels of logging, and has more ways of outputting your logged messages (including the RollingFileAppender, which is a nice touch). To use it isn’t very hard, although the documentation doesn’t make this terribly clear.

In Log4Net you can define many loggers. These loggers are named, usually the fully specified name of the class that they apply to (e.g. Ford.Vehicles.Cars.FocusSport), though that is not necessarily the case.

They exist in a kind of hierarchy. There is a Root logger, which applies to the whole application, and then loggers inherit from their ‘parent’. For instance, if a log was specified for “Ford.Vehicles”, then “Ford.Vehicles.Cars” would inherit it’s logging settings – although it could override them. This is why the fully specified class name is normally used – conveniently, it forms this hierarchy.

Anyway, in your application you create a logger. When you use this to log messages, the logger passes the message to any appenders attached to it. They control recording the message to whatever output they were designed for. Sometimes you may have formatters to control what the output looks like.

Enough of that, how does this work?

Okay, the quickstart.

1) Add the Log4NET.dll into your project as a reference. (You’ll have to deploy this DLL when you install your final application).

2) In your ‘main’ class, add the following tag just above the namespace:

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

This tells the logging framework to ‘watch’ the config file. It will then pick up any changes to logging levels, etc., as they happen (rather than having to restart).

3) In the class you want to put logging in, create a logger object:

log4net.ILog log = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType );

This line uses reflection to get the full class name of the class it is in automatically, which is nice – it makes it a cut and paste operation.

4) Make your logging calls in your code:

log.Fatal("There has been an exception");

5) In your App.config files add a new Configuration Section:

<configSections >
<section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
</configSections>

6) Finally, configure your appenders, loggers, etc.. See http://logging.apache.org/log4net/ for more details, but my example is:

<log4net>
<!-- Define some output appenders -->
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<threshold value="Warn" />
<ApplicationName value="Invoice Email Notifier" />
<LogName value="Deltascheme Notification Mailer Service" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>

<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="debug.txt" />
<appendToFile value="true" />
<datePattern value="yyyyMMdd" />
<rollingStyle value="Size" />
<maximumFileSize value="1MB" />
<filter type="log4net.Filter.LevelRangeFilter">
<acceptOnMatch value="true" />
<levelMin value="INFO" />
<levelMax value="WARN" />
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5p %d %-35.35c{1} %-27.27M - %m%n" />
</layout>
</appender>

<!-- Setup the root category, add the appenders and set the default level -->
<root>
<level value="ALL" />
<appender-ref ref="EventLogAppender" />
<appender-ref ref="RollingLogFileAppender" />
</root>

</log4net>

This defines a Root logger (which logs everything), and adds two appenders to it. One appender only records Warning, Error or Fatal messages, which it puts into the Windows Event Log. The other appender writes to a log file called ‘debug.txt’. This log file is limited to 1Mb in size, after which old records are pushed off the end of the file (which is a nice feature). It also has a filter, and will only record messages that are Information or Warning messages.

Advertisements
Logging, the .NET Framework, and why I used Log4NET

One thought on “Logging, the .NET Framework, and why I used Log4NET

  1. Humm… interesting,

    Good tip I agree with yuo this one is more flexible and is easier to understand

    Thanks for writing, most people don’t bother.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s