Add logging to your application using log4net (part two)

Add logging to your application using log4net

Picking up the code from the previous post, let’s add another appender to the config file as this helps to demonstrate the relationship between loggers and appenders.

log4net provides an SmtpAppender which will send out log messages via email. This is useful for high priority messages such as errors, as the recipients are notified of a problem immediately rather than being contacted by disgruntled users. There is also no need to search the log files as the relevant information is contained in the email. The following snippet is an example of how to configure the SmtpAppender

<appender name="EmailAppender" type="log4net.Appender.SmtpAppender">
    <to value="foo@bar.com" />
    <from value="LoggingDemo website &lt;email.appender@foo.com&gt;" />
    <subject value="Message from LoggingDemo website" />
    <smtpHost value="exchange.foo.com" />
    <bufferSize value="0" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{yyyy-MM-dd HH:mm:ss.fff} %-5level %message%newline" />
    </layout>
</appender>

The majority of the settings are self-explanatory, however it is worth noting that buffer size is set to zero. Had this been omitted the default of 512 would have been used, meaning that only when the buffer had 512K or more of logs would an email be sent, containing all of the messages. Setting the buffer size to zero will cause each log message to be sent in a separate email.

The appender can be added to the root logger using an appender-ref node in exactly the same way that the text file appender was. If you run the sample site again a log message will be appended to the LoggingDemo.log file and sent to the specified email address.

Capturing more detail about exceptions

So far we’ve just been logging simple messages. In the case of logging errors, messages such as “an error occurred”, or even the exception’s Message property, do not provide enough detail to diagnose a fault. However the logging methods (Warn, Error, and so on) on the ILog interface actually allow any object to be passed as the message – it does not have to be a string. So if we pass in an Exception object log4net will transform that into text before passing it to all of the logger’s appenders. If you try this you’ll see that properties such as the type of exception, the message and the stack trace are all recorded.

This is a reasonable solution although still short of the ideal. Other properties of the exception such as TargetSite, Source and Data (a little-known but useful dictionary in which extra error information can be stored) are not logged. Furthermore, in a web scenario you may wish to log data such as the URL of the request, the session ID, and so on.

What is needed is complete control over how an exception message is formatted before being sent to the appenders. This is where the IObjectRenderer interface comes in. We can create an exception renderer which, when registered in the config file, will be used each time log4net needs to convert an Exception into text before it is logged. So let’s do just that.

Creating and configuring the ExceptionRenderer class

Add a new class to the project called ExceptionRenderer, import the log4net.ObjectRenderer namespace, and make the class implement the IObjectRenderer interface. There is just one method to implement, RenderObject, and it provides us with a TextWriter to write our message with. From here it is pretty simple to cast the obj argument to an Exception and start writing out the properties, as shown below

public class ExceptionRenderer : IObjectRenderer
{
    public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
    {
        Exception thrown = obj as Exception;
        while (thrown != null)
        {
            RenderException(thrown, writer);
            thrown = thrown.InnerException;
        }
    }

    private void RenderException(Exception ex, TextWriter writer)
    {
        writer.WriteLine(string.Format("Type: {0}", ex.GetType().FullName));
        writer.WriteLine(string.Format("Message: {0}", ex.Message));
        writer.WriteLine(string.Format("Source: {0}", ex.Source));
        writer.WriteLine(string.Format("TargetSite: {0}", ex.TargetSite));
        RenderExceptionData(ex, writer);
        writer.WriteLine(string.Format("StackTrace: {0}", ex.StackTrace));
    }

    private void RenderExceptionData(Exception ex, TextWriter writer)
    {
        foreach (DictionaryEntry entry in ex.Data)
        {
            writer.WriteLine(string.Format("{0}: {1}", entry.Key, entry.Value));
        }
    }
}

Note that the while loop ensures that all inner exceptions are also logged.

To make log4net aware of the renderer add the following line of XML under the root log4net node

<renderer renderingClass="LoggingDemo.ExceptionRenderer, LoggingDemo" renderedClass="System.Exception" />

The important point to note here is that the fully-qualified names of the classes must be used, much like when defining the appender’s type.

In order to test the renderer add a button to the default page and in its click handler add a call to a method which throws an exception. Then wrap this call in a try-catch block and, in the catch part, log the exception by passing it into the logger’s Error method. The code below should suffice

protected void ThrowButton_Click(object sender, EventArgs e)
{
    try
    {
        DoSomething();
    }
    catch (Exception ex)
    {
        ILog logger = LogManager.GetLogger(string.Empty);
        logger.Error(ex);
    }
}

private void DoSomething()
{
    ApplicationException ex = new ApplicationException("DoSomething() has failed.");
    ex.Data.Add("SomeKey", "SomeValue");
    ex.Data.Add("AnotherKey", "AnotherValue");
    throw ex;
}

The log file should now contain a well-formatted message listing all of the exception’s properties. As projects develop the ExceptionRenderer can be enhanced to record any other information you see fit.

Going further

The idea of producing custom renderers extends beyond exceptions. I mentioned logging web-specific data earlier; the best approach to this is to implement an HttpContextRenderer which would then output the URL, the query string, etc. Then the HttpContext object itself can just be passed straight into one of the logging methods of the logger.

The sample web application for this post is available here.

Tags: ,

3 Responses to “Add logging to your application using log4net (part two)”

  1. You’ve wrote a very excellent blog post.
    If it’s ok with you, I would like to ask permission to use your article as it relates to my problem. I will be glad to negotiate to pay you or hire you for this.

    With Regards from
    Republic Polytechnic

  2. Amna says:

    Very very helpful!! I was looking for logging exception data with log4net and couldn’t find a solution better than yours!

  3. Sweet. Thank you. Very informative

Leave a Reply