Log4Net ScrollingTextBox
I use log4net for most of my apps. I did similar things in all the languages i've worked with. I just like having a way to verbosely spew information into the ether so that i could use for analysing code when something gest wonky without having to step through the code or go uncommenting loads of Console.Writeline() calls.
On unix, i just always have a terminal open for tailing the log files and while i often do the same under Windows, using a Cygwin bash shell, i've started putting debug windows into my Windows.Forms apps, just because it's nice and convenient. To make this work, you simply use the log4net MemoryAppender. For display purposed, I use a TextBox, but do some legwork to limit the size of the text logged and to make sure it stays scrolled at the bottom.
public class FormX : System.Windows.Forms.Form { #region Static Members #################################################### private static readonly ILog log = LogManager.GetLogger(typeof(FormX)); #endregion #region Members Variables ################################################# private bool logWatching = true; private log4net.Appender.MemoryAppender logger; private Thread logWatcher; /// <summary> /// The TextBox for our logging messages /// </summary> private System.Windows.Forms.TextBox mLog; /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null; #endregion #region Constructors ################################################# public FormX() { // // Required for Windows Form Designer support // InitializeComponent(); this.Closing += new CancelEventHandler(FormX_Closing); logger = new log4net.Appender.MemoryAppender(); // Could use a fancier Configurator if you don't want to catch every message log4net.Config.BasicConfigurator.Configure(logger); // Since there are no events to catch on logging, we dedicate // a thread to watching for logging events logWatcher = new Thread(new ThreadStart(LogWatcher)); logWatcher.Start(); } #endregion // [...] private void FormX_Closing(object sender, CancelEventArgs e) { // Gotta stop our logging thread logWatching = false; logWatcher.Join(); } private void LogWatcher() { // we loop until the Form is closed while(logWatching) { LoggingEvent[] events = logger.Events; if( events != null && events.Length > 0 ) { // if there are events, we clear them from the logger, // since we're done with them logger.Clear(); foreach( LoggingEvent ev in events ) { StringBuilder builder; // the line we want to log string line = ev.LoggerName + ": " + ev.RenderedMessage+"\r\n"; // don't want to grow this log indefinetly, so limit to 100 lines if( mLog.Lines.Length > 99 ) { builder = new StringBuilder(mLog.Text); // strip out a nice chunk from the beginning builder.Remove(0,mLog.Text.IndexOf('\r',3000)+2); builder.Append(line); mLog.Clear(); // using AppendText since that makes sure the TextBox stays // scrolled at the bottom mLog.AppendText(builder.ToString()); } else { mLog.AppendText(line); } } } // nap for a while, don't need the events on the millisecond. Thread.Sleep(500); } } }

4 Comments:
Hi
Very nice code...
But there are two big problems in your code...
1. You are changing content of the control mLog. It is not obvious from your code, but I suggest that you are creting this control in the main thread. It is not thread safe to create control in one thread and to change it from another.
2. MemoryAppender is also not thread safe. So if I log some info from another thread like 'LogWatcher' it is not sure, that it will be corectly displayd. (my log action from another thread will occure between logger.Events and logger.Clear())
Do you know some sollution for the second problem? (the first one is possible to solve through Invoke method)
Thanks
mtv
mtv,
I believe problem 1) is addressed with my revised version here. This code uses BeginInvoke to make sure that the update happens on the UI thread.
Problem 2) would explain why i still get the occasional lock-ups. That could also be solved by using begininvoke and either handling the whole memoryappender issue on the UI thread or creating a separate thread for the appender and invoking actions on that thread via BeginInvoke. The first option is easier to write, but may impact the UI responsiveness.
HTH,
ether
mtv,
regarding problem 2).. What I previously wrote won't do the trick. What needs to happen is that your log.Debug() calls happen on one thread. So you basically need a wrapper for it that invokes all logging on a single thread.
Hi,
Thank you very match. I've used your code in my project, but I want to say, that add text to control is not thread safe. I've added some codes for making calls thread-safe (How to: Make Thread-Safe Calls to Windows Forms Controls). I've added msgs to ListBox therefore I can add code only for ListBox
delegate void AddMsgCallback(string msg);
private void AddMsg(string msg)
{
if (this.lbLog.InvokeRequired)
{
AddMsgCallback d = new AddMsgCallback(AddMsg);
this.Invoke(d, new object[] { msg });
}
else
{
if (this.lbLog.Items.Count > 1000)
{
this.lbLog.Items.RemoveAt(0);
}
this.lbLog.Items.Add(msg);
}
}
Post a Comment
<< Home