Wednesday, December 15, 2010

Log4Net Appender for Displaying Messages on a Windows Form Application

Yesterday, I ran into a situation where I needed to display logging information on a WinForm application. The API that I wanted to show logging for was already using log4net for all of its logging, so the simplest way to accomplish my goal was to implement a custom log4net appender. The code below was the minimum that I needed, but there is room for enhancements as noted in the comments.

using System;
using System.Windows.Forms;
using log4net;
using log4net.Appender;
using log4net.Repository.Hierarchy;
public class TextBoxAppender : IAppender
{
    private TextBox _textBox;
    private readonly object _lockObj = new object();

    public TextBoxAppender(TextBox textBox)
    {
        var frm = textBox.FindForm();
        if(frm==null)
            return;

        frm.FormClosing += delegate { Close(); };

        _textBox = textBox;
        Name = "TextBoxAppender";
    }

    public string Name { get; set; }

    public static void ConfigureTextBoxAppender(TextBox textBox)
    {
        var hierarchy = (Hierarchy)LogManager.GetRepository();
        var appender = new TextBoxAppender(textBox);
        hierarchy.Root.AddAppender(appender);
    }

    public void Close()
    {
        try
        {
            // This locking is required to avoid null reference exceptions
            // in situations where DoAppend() is writing to the TextBox while
            // Close() is nulling out the TextBox.
            lock (_lockObj)
            {
                _textBox = null;
            }

            var hierarchy = (Hierarchy)LogManager.GetRepository();
            hierarchy.Root.RemoveAppender(this);
        }
        catch
        {
            // There is not much that can be done here, and
            // swallowing the error is desired in my situation.
        }
    }

    public void DoAppend(log4net.Core.LoggingEvent loggingEvent)
    {
        try
        {
            if (_textBox == null)
                return;

            // For my situation, this quick and dirt filter was all that was 
            // needed. Depending on your situation, you may decide to delete 
            // this logic, modify it slightly, or implement something a 
            // little more sophisticated.
            if(loggingEvent.LoggerName.Contains("NHibernate"))
                return;

            // Again, my requirements were simple; displaying the message was
            // all that was needed. Depending on your circumstances, you may
            // decide to add information to the displayed message 
            // (e.g. log level) or implement something a little more 
            // dynamic.
            var msg = string.Concat(loggingEvent.RenderedMessage, "\r\n");

            lock (_lockObj)
            {
                // This check is required a second time because this class 
                // is executing on multiple threads.
                if (_textBox == null)
                    return;

                // Because the logging is running on a different thread than
                // the GUI, the control's "BeginInvoke" method has to be
                // leveraged in order to append the message. Otherwise, a 
                // threading exception will be thrown. 
                var del = new Action<string>(s => _textBox.AppendText(s));
                _textBox.BeginInvoke(del, msg);
            }
        }
        catch
        {
            // There is not much that can be done here, and
            // swallowing the error is desired in my situation.
        }
    }
}


To wirer this appender up to a TextBox, simply use the following code.
//Where "LoggingTextBox" is a TextBox having the following settings:
//LoggingTextBox.Multiline = true;
//LoggingTextBox.ReadOnly = true;
//LoggingTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;

TextBoxAppender.ConfigureTextBoxAppender(LoggingTextBox);

2 comments: