How to watch your log through your application in Log4Net

Log4Net is an excellent logging system that allows you to clearly see what your application is doing and when, making it very easy to debug your app during development but especially during production. I have previously demonstrated how to use Log4Net in C# which enables you to log output to your console for the former and to a file for the latter, but recently I had a requirement to also log to screen during production. This is how I achieved it:

There is already existing an appender that allows you to log your events to memory; I felt this was a good place to start as it means I can immediately access the events with little fuss, however, it doesn’t contain the ability to throw events when an event is added and as I want to view events in real time this required extending the class:

public class MemoryAppenderWithEvents : MemoryAppender {
    public event EventHandler Updated;

    protected override void Append(log4net.Core.LoggingEvent loggingEvent) {
        // Append the event as usual
        base.Append(loggingEvent);

        // Then alert the Updated event that an event has occurred
        var handler = Updated;
        if (handler != null) {
            handler(this, new EventArgs());
        }
    }
}

This is pretty self explanatory – it simply carries out the usual action of adding the event that occurs but it also calls the Updated event with blank EventArgs (the purpose being to simply alert us).

We then add this MemoryAppenderWithEvents to our Log4Net.config where [namespace] is your working namespace:

<!-- Append to memory (for displaying in log console) -->
<appender name="MemoryAppender" type="[namespace].MemoryAppenderWithEvents" />

NOTE: Todd kindly points out that you may need to add the assembly name after the type, see: http://weblogs.asp.net/tgraham/loading-the-assembly-for-a-custom-log4net-appender

and then within your root tag insert:

<appender-ref ref="MemoryAppender" />

The next step is to create a LogWatcher – this monitors the MemoryAppenderWithEvents for Updated events and reacts when one occurs by throwing its own event. These two classes could probably have been combined but in my case it was best to keep them separate:

public class LogWatcher {
    private string logContent;

    private MemoryAppenderWithEvents memoryAppender;

    public event EventHandler Updated;

    public string LogContent {
        get { return logContent; }
    }

    public LogWatcher() {
        // Get the memory appender
        memoryAppender = (MemoryAppenderWithEvents) Array.Find(LogManager.GetRepository().GetAppenders(), GetMemoryAppender);

        // Read in the log content
        this.logContent = GetEvents(memoryAppender);

        // Add an event handler to handle updates from the MemoryAppender
        memoryAppender.Updated += HandleUpdate;
    }

    public void HandleUpdate(object sender, EventArgs e) {
        this.logContent += GetEvents(memoryAppender);

        // Then alert the Updated event that the LogWatcher has been updated
        var handler = Updated;
        if (handler != null) {
            handler(this, new EventArgs());
        }
    }

    private static bool GetMemoryAppender(IAppender appender) {
        // Returns the IAppender named MemoryAppender in the Log4Net.config file
        if (appender.Name.Equals("MemoryAppender")) {
            return true;
        } else {
            return false;
        }
    }

    public string GetEvents(MemoryAppenderWithEvents memoryAppender) {
        StringBuilder output = new StringBuilder();

        // Get any events that may have occurred
        LoggingEvent[] events = memoryAppender.GetEvents();

        // Check that there are events to return
        if (events != null && events.Length > 0) {
            // If there are events, we clear them from the logger, since we're done with them  
            memoryAppender.Clear();

            // Iterate through each event
            foreach (LoggingEvent ev in events) {
                // Construct the line we want to log
                string line = ev.TimeStamp.ToString("yyyy-MM-dd HH:mm:ss,fff") + " [" + ev.ThreadName + "] " + ev.Level + " " + ev.LoggerName + ": " + ev.RenderedMessage + "\r\n"; 

                // Append to the StringBuilder
                output.Append(line);
            }
        }

        // Return the constructed output
        return output.ToString();
    }
}

So we first of all create the LogWatcher and find the MemoryAppenderWithEvents (in this case named MemoryAppender) and we get any events that may already be in it which are written to our buffer; we then attach a function to the MemoryAppenderWithEvents’ Updated event so that when the MemoryAppenderWithEvents is updated it will again write the events to the buffer but then also call the LogWatcher’s own Updated event.

So now we are collecting events in to a buffer whenever these events occur, we need to actually write them to somewhere. In my case I am writing them to a RichTextBox called LogTextbox (which we will presume already exists); so lets create and initialise the LogWatcher and attach a function to the Updated event:

// Create a LogFileWatcher to display the log and bind the log textbox to it
logWatcher = new LogWatcher();
logWatcher.Updated += logWatcher_Updated;

and lets create the function:

public void logWatcher_Updated(object sender, EventArgs e) {
    UpdateLogTextbox(logWatcher.LogContent);
}

This function simply calls a public form function that will do the actual updating. This is very important because if we update the textbox directly it will throw an error as both items are created on different threads:

public void UpdateLogTextbox(string value) {
    // Check whether invoke is required and then invoke as necessary
    if (InvokeRequired) {

        this.BeginInvoke(new Action(UpdateLogTextbox), new object[] { value });
        return;
    }

    // Set the textbox value
    LogTextbox.Text = value;
}

And we’re done! You should now have a reliable logging textbox that shows all of the events within it that also appear within your console, your textfile and wherever else you have logged events. There are a couple of great additions that you can make to your console log and I will be adding this in some upcoming posts, so keep an eye out if you are interested.

About Stephen Pickett


Stephen Pickett is a programmer, IT strategist, project manager, RightNow and telephony expert, information security specialist, all-round geek. He is currently Professional Services Director at Connect Assist, a social business that helps charities and public services improve quality, efficiency and customer engagement through the provision of helpline services and CRM systems.

Stephen is based in south Wales and attended Cardiff University to study Computer Science, in which he achieved a 2:1 grading. He has previously worked for Think Consulting Solutions, the leading voice on not-for-profit fundraising, Fujitsu Services and Sony Manufacturing UK as a software developer.

Stephen is the developer of ThinkTwit, a WordPress plugin that allows you to display multiple Twitter feeds within a blog.

23 thoughts on “How to watch your log through your application in Log4Net

  1. Excellent work.

    With method HandleUpdate, I think it also always appending log messages, so I changed the line below:
    from: this.logContent += GetEvents(memoryAppender);
    to: this.logContent = GetEvents(memoryAppender);

    Thanks again and great work.

    David Selwood.

  2. Hi David,

    Thanks for your kind comments. It’s been a while since I posted this, but if I recall correctly I was appending the events to a string and this string is simply displayed within the RichTextBox whenever the form was displayed, and on any updated the RichTextBox simply refreshes itself with the full contents of the string. In this case appending is fine, but I do understand in different implementations you may not necessarily want to do this.

    I think the key thing to remember is that GetEvents utilises the MemoryAppender’s GetEvents function which clears out any old events when the method is called (again, if I recall correctly).

  3. Hi David

    I was trying to use above code to show live update of logs on pop up window.If I close and reopen the log pop up window new notification are not updating properly GetEvents returning the empty string but sometimes I do get proper update.I am using here WPF with richtextbox control to display logs.

  4. Hi Himanshu,

    When you close the pop up window are you just hiding it or killing it? Where are you storing the logging information? You need to make sure that the logging information is available within the pop up always, if you are killing it you should ensure then that it is stored in the same place that you load it – ideally central to the application.

    Do some testing to try to determine what conditions are causing the “sometimes” that you refer to and this should help you work out where you are losing the data.

    Hope that is helpful.

  5. Hi Stephen

    Thanks for your valuable comments, my approach is to store logs in text file and using your code.After closing the log viewer pop up window and reopen it again I first refer log text file (clear textbox text), any updates after that are fetched using your code,I tried with both hiding and killing window but had same effect.Any suggestions will be appreciated.

  6. Hi Himanshu,

    From what you’ve described there’s nothing rally obvious – if you’re able to provide me with your E-mail address I’ll get in touch so that you can send me your code for me to take a look.

  7. Stephen, I’m glad you posted this. I write in C# and VB, but this app I need it in is in VB, and I’m having some trouble with just one line of code in the conversion/translation. If this is a code issue, and not the logger, I’m sorry in advance! 🙂

    I made it simple by grabbing a screenshot in VS2012, putting it here: http://atcp.us/problems/VB.NET/Screenshot.jpg

    Basically, this line:

    logWatcher = New LogWatcher()
    logWatcher.Updated += logWatcher_Updated

    errors out with 3 errors:

    1. Error 32 ‘Public Event Updated(sender As Object, e As System.EventArgs)’ is an event, and cannot be called directly. Use a ‘RaiseEvent’ statement to raise an event.

    2. Error 33 Argument not specified for parameter ‘e’ of ‘Public Sub logWatcher_Updated(sender As Object, e As System.Windows.RoutedEventArgs)’.

    3. Error 34 Argument not specified for parameter ‘sender’ of ‘Public Sub logWatcher_Updated(sender As Object, e As System.Windows.RoutedEventArgs)’.

    …which kind of makes sense, as the sub logWatcher_Updated is:

    Public Sub logWatcher_Updated(sender As Object, e As RoutedEventArgs)
    UpdateLogTextbox(LogWatcher.LogContent)
    End Sub

    …I, like you did, need to send logs from log4net to the UI…

    Thanks very much in advance… I will post the VB version of this to my blog, giving credit, once I get it working! 🙂

    pat
    🙂

  8. Hi Pat,

    Wow, now you’re testing me – it’s a while since I wrote this and far longer since I last wrote any VB. A quick Google on how to add event handler in VB.net tells me that the following should work:

    AddHandler logWatcher.Updated, AddressOf logWatcher_Updated

    Give it a go and let me know if it solves your issue!

  9. Hi Stephen, really good article. I know its been awhile since you’ve wrote this but I was wondering for :
    logWatcher = New LogWatcher()
    logWatcher.Updated += logWatcher_Updated

    What else needs to be done for it work? I followed everything as written but keep getting ‘logWatcher is a method but is used like a type’ and if I make it the method, then I get the error ‘logWatcher is a field but is used like a ‘type.” Also, this should all be in the LogWatcher class right?

  10. Hi CJ, are you using C# to write your code? If so what version of .Net? The reason I ask is because your syntax looks more VB than C# to me and this could indicate the issue – you would need to re-write the code in VB as Bogus Exception has done 2 posts before yours.

  11. Yea C# 4.5.
    I have it as:
    Logwatch log = new LogWatcher(); and the second part the same way.
    Going by the steps, I wasn’t sure where exactly those two lines should be.

  12. Hi Ross, sorry for the delay in getting back to you – things have been pretty busy for me lately! ReflectInsight looks quite interesting, thanks for raising it to my attention – I think it could be quite valuable when debugging projects, especially during development. Have you tried it? What’s your take?

  13. Hi Stephen,

    I am not trying to pick apart your example. However I noticed another issue you might want to correct.

            public void HandleUpdate(object sender, EventArgs e)
            {
    [I changed this line otherwise it duplicates the previous log entry]
                //this.logContent += GetEvents(memoryAppender); 
    

    [To]

                this.logContent = GetEvents(memoryAppender);
    
    
                // Then alert the Updated event that the LogWatcher has been updated
                var handler = Updated;
                if (handler != null)
                {
                    handler(this, new EventArgs());
                }
            }
    

    Thanks again!

    Todd

  14. Hi Stephen ,

    Nice Article. I have one problem though. When I have written Log.DebugFormat in my code,the Updated handler becoming null. The event is raised but cannot go in as Updated is null.

    // Then alert the Updated event that an event has occurred
    var handler = Updated;
    if (handler != null)
    {
    handler(this, new EventArgs());
    }

    Request you to help me to overcome this. Thanks

  15. Hi Ayyappa,

    It’s 2 years since I wrote this article and I’m not doing any C# programming at the moment so finding it difficult to assist you.

    I presume you have tested with just Log.Debug and the event is raised correctly?

    I have found out that formatting methods e.g. DebugFormat, do not use Object Renderers (http://logging.apache.org/log4net/release/manual/introduction.html#renderers) which could be a route to investigate (if you had been using one yourself then this could explain the issue).

    Beyond that I would look at the implementation of DebugFormat and see how it differs to Debug to determine what is different for it to affect your handler.

    Good luck!

  16. Hello,

    Just a very small error in the code. It isn’t:
    this.BeginInvoke(new Action(UpdateLogTextbox), new object[] { value });

    But:
    this.BeginInvoke(new Action(UpdateLogTextbox), new object[] { value });

    Thank you for it! Very usefull!

Leave a Reply