Log4Net Appender for Displaying Messages on a Windows Form Application

15 December 2010

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.

 1 using System;
 2 using System.Windows.Forms;
 3 using log4net;
 4 using log4net.Appender;
 5 using log4net.Repository.Hierarchy;
 6 public class TextBoxAppender : IAppender
 7 {
 8  private TextBox _textBox;
 9  private readonly object _lockObj = new object();
10 
11  public TextBoxAppender(TextBox textBox)
12  {
13      var frm = textBox.FindForm();
14      if(frm==null)
15          return;
16 
17      frm.FormClosing += delegate { Close(); };
18 
19      _textBox = textBox;
20      Name = "TextBoxAppender";
21  }
22 
23  public string Name { get; set; }
24 
25  public static void ConfigureTextBoxAppender(TextBox textBox)
26  {
27      var hierarchy = (Hierarchy)LogManager.GetRepository();
28      var appender = new TextBoxAppender(textBox);
29      hierarchy.Root.AddAppender(appender);
30  }
31 
32  public void Close()
33  {
34      try
35      {
36          // This locking is required to avoid null reference exceptions
37          // in situations where DoAppend() is writing to the TextBox while
38          // Close() is nulling out the TextBox.
39          lock (_lockObj)
40          {
41              _textBox = null;
42          }
43 
44          var hierarchy = (Hierarchy)LogManager.GetRepository();
45          hierarchy.Root.RemoveAppender(this);
46      }
47      catch
48      {
49          // There is not much that can be done here, and
50          // swallowing the error is desired in my situation.
51      }
52  }
53 
54  public void DoAppend(log4net.Core.LoggingEvent loggingEvent)
55  {
56      try
57      {
58          if (_textBox == null)
59              return;
60 
61          // For my situation, this quick and dirt filter was all that was 
62          // needed. Depending on your situation, you may decide to delete 
63          // this logic, modify it slightly, or implement something a 
64          // little more sophisticated.
65          if(loggingEvent.LoggerName.Contains("NHibernate"))
66              return;
67 
68          // Again, my requirements were simple; displaying the message was
69          // all that was needed. Depending on your circumstances, you may
70          // decide to add information to the displayed message 
71          // (e.g. log level) or implement something a little more 
72          // dynamic.
73          var msg = string.Concat(loggingEvent.RenderedMessage, "\r\n");
74 
75          lock (_lockObj)
76          {
77              // This check is required a second time because this class 
78              // is executing on multiple threads.
79              if (_textBox == null)
80                  return;
81 
82              // Because the logging is running on a different thread than
83              // the GUI, the control's "BeginInvoke" method has to be
84              // leveraged in order to append the message. Otherwise, a 
85              // threading exception will be thrown. 
86              var del = new Action<string>(s => _textBox.AppendText(s));
87              _textBox.BeginInvoke(del, msg);
88          }
89      }
90      catch
91      {
92          // There is not much that can be done here, and
93          // swallowing the error is desired in my situation.
94      }
95  }
96 }

To wire this appender up to a TextBox, simply use the following code.

1 //Where "LoggingTextBox" is a TextBox having the following settings:
2 //LoggingTextBox.Multiline = true;
3 //LoggingTextBox.ReadOnly = true;
4 //LoggingTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
5 
6 TextBoxAppender.ConfigureTextBoxAppender(LoggingTextBox);
Update: 2/27/2014
The post above was originally posted on BlogSpot on 12/15/2010. I have relocated it here. However, based on previous feedback, I have added additional information on how to configure this appender below.

To add TextBoxAppender to your logging stack, you simply have to update you app.config file and add the appender in the same manor as you would for out-of-the-box appenders. Here is an example of how it is done.

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <configuration>
 3   <configSections>
 4     <section name="log4net"
 5       type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
 6   </configSections>
 7   <log4net>
 8     <appender name="TextBoxAppender" 
 9       type="WindowsFormsLog4NetAppenderExample.TextBoxAppender" />
10     <root>
11       <level value="DEBUG" />
12       <appender-ref ref="TextBoxAppender" />
13     </root>
14   </log4net>
15 </configuration>

Please note that because the appender is very simple, some of the standard configuration settings such as "pattern" will have no effect. Also note, in my sample config file the type is "WindowsFormsLog4NetAppenderExample.TextBoxAppender". You will need to tweak that type name based on the namespace that you copy this code into. For example, your type might be something like "MyNamespace.TextBoxAppender".

If you would like to see the source code for a working example, I have posted it on GitHub.