Click or type ~ to show Console
Type Ctrl-C to close console. Type 'wat?' to find out how this console was created.
Welcome to the Iloggable Interactive Console. You can navigate posts either by file hierarchy with cd [path] or via paged posts lists using the posts [page] command. You can navigate to a new post with the go [path|#id].

ILoggable

A place to keep my thoughts on programming

June 1, 2005 .net

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);
        }
    }
}

3 to “Log4Net ScrollingTextBox”

  1. ghedgehog says...

    Many thanks for illustrating the use of MemoryAppender which was very helpful.

    I have overcome problem (2), i.e. log action between logger.Events and logger.Clear(), by sub-classing MemoryAppender as follows:

    public class MyMemoryAppender : MemoryAppender
    {
    private object _lockObj = new object();

    protected override void Append(LoggingEvent loggingEvent)
    {
    lock (_lockObj)
    {
    base.Append(loggingEvent);
    }
    }

    public LoggingEvent[] ExtractEvents()
    {
    lock (_lockObj)
    {
    LoggingEvent[] events = GetEvents();
    Clear();
    return events;
    }
    }
    }

  2. Casper says...

    Why did u initiliazed "log" when you don't use it? You could have implemented it and then you get notified about events (in a event-driven architecural way), without the need for any polling and overhead for conditional statements.
    Anway, good example of usage of "MemoryAppender".

  3. arne says...

    Casper,

    the // [...] implies there is ommitted code which uses the log member.

    As for getting log events in an event driven manner, ILogger does not have any events on it. In order to receive logging events as .NET events i'd have to create a custom implementation of ILogger, which would have two problems:

    • All my code would use it, plus code i don't control would then not be observable, since it would use the standard facility
    • I'd loose all the configuration flexibility of log4net's plumbing like setting log levels, and filtering, etc.

Leave a comment