Sunday, March 18, 2007

Multithread logger in WPF

So, we already know to write very nice WPF application, but one of most common modules in every application is logger. How to use WPF capabilities to build smart logger. What to do with multithreading environment, when methods InvokeRequired is absent?

Today, we'll build logger, based on DataBinding of WPF, learn how to know what thread we are in and how to bind resources to ListView. Pretty enough? Let's start

First of all we have to build our XAML page. It sill consists of checkbox, binded to static variable that tells us if we have to log everything. So, we'll add simple boolean, named m_isDebug and bind it to the checkbox. But how? m_isDebug is simple variable, not dependency property, how to get it and bind to control. This part is really simple. We'll do it this way ("l" is our clr-namespace: xmlns:l="clr-namespace:WPFLogger")

<CheckBox IsChecked="{x:Static l:Window1.m_isDebug}">Debug view</CheckBox>

So far, so good. Now the main part of the application, our XmlDataSource - the log file. It's really simple to add the structure to our XAML

<Window.Resources>

  <XmlDataProvider x:Key="myDebugData" XPath="/myXmlData">
    <x:XData>
      <myXmlData xmlns="">
        <Item Date="00/00/00 00:00:00" Source="None" Message="Testing..."/>
      </myXmlData>
    </x:XData>
  </XmlDataProvider>
</Window.Resources>

Event simpler to get it from the code



protected void OnLoad(object sender, RoutedEventArgs e)


        {
            debug = this.Resources["myDebugData"] as XmlDataProvider;
            debug.IsAsynchronous = true;
            WriteLog(this, "Initialized...");            
        }


Let's build the static method, that knows to write to this Data Provider. This method should know where he called from in order us to be able to get debug information later. We do not have to leave our project in Debug mode to get StackTrace information. So, the method will looks like this:



if (debug != null && m_isDebug)
                {
                    System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(false);
 
 
                    XmlElement item = debug.Document.CreateElement("Item");
 
                    item.SetAttribute("Date", DateTime.Now.ToString("dd-MMM-yy HH:mm:ss.ffff"));
                    item.SetAttribute("Source", sender!=null?sender.ToString() + ".":Thread.CurrentThread.Name + stack.GetFrame(1).GetMethod().Name);
                    item.SetAttribute("Message", data);
 
                    debug.Document.SelectSingleNode(debug.XPath).AppendChild(item);
                }

StackTrace will tell us where we are together will caller attribute or thread information, if inaccessible. We are speaking about threads, is this method is thread safe? Not really. We are interacting with "debug" object, and updates ListView, so we have to be sure that we are in the right (STA) thread. There is no such thing InvokeRequired in WPF, due the new object, named Dispatcher tries to help us with thread synchronization. It knows what thread the caller exists and make us able to BeginInvoke and invoke our method within the right context. In order to get such information, we add a couple of things. First of all delegate to this method, next we'll add to the method check of context and then invoke or execute the rest



internal delegate void WriteLogDelegate(object sender, string data);
 
internal static void WriteLog(object sender, string data)
        {
            if (!Application.Current.Dispatcher.CheckAccess())
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new WriteLogDelegate(WriteLog), sender, data);
            }
            else
            {
                if (debug != null && m_isDebug)
                {
                    System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(false);
 
 
                    XmlElement item = debug.Document.CreateElement("Item");
 
                    item.SetAttribute("Date", DateTime.Now.ToString("dd-MMM-yy HH:mm:ss.ffff"));
                    item.SetAttribute("Source", sender!=null?sender.ToString() + ".":Thread.CurrentThread.Name + stack.GetFrame(1).GetMethod().Name);
                    item.SetAttribute("Message", data);
 
                    debug.Document.SelectSingleNode(debug.XPath).AppendChild(item);
                }
            }
        }

The rest is really simple, we'll add listview, binded to our xml data provider, a couple of nice features, like saving, sorting etc within the data source. All the rest you'll see in the source code attached.


The usage of new WPF logger is really simple. Just call WriteLog(object, string) from any place in any thread you want and the result will appears in listbox immediately. The are a couple of FXes I'd add to the logger, such as priority, display or write flag and more, But all those are really application specific, so do it yourself :)


Source code for this article

1 comment:

Boga said...

Unfortunately your source code downloads don't work :( Great Articles though! Please update the download links! Thank you.