Kirk Evans Blog

.NET From a Markup Perspective

A Better ExceptionXmlPublisher for the Exception Management Application Block.

A Better ExceptionXmlPublisher for the Exception Management Application Block.

  • Comments 5
This snippet incorporates part of a post from microsoft.public.dotnet.framework.  It shows how to allow multiple threads to write to the same file at once, using a mutex to provide concurrency.  This example is integrated with the Exception Management Block to provide a means of logging errors in multi-user applications to XML files.

using System;
using System.Text;
using System.IO;
using System.Xml; 
using System.Collections;
using System.Collections.Specialized;
using Microsoft.ApplicationBlocks.ExceptionManagement;
using System.Threading;
using System.Diagnostics;

namespace DistributedArchitects.Exceptions
{
 /// <SUMMARY>
 /// Logs the XML data to the specified file.
 /// </SUMMARY>
 public class ExceptionXmlPublisher : IExceptionXmlPublisher
 {
  /// <SUMMARY>
  /// Called by ExceptionManager through IExceptionXmlPublisher interface. 
  /// </SUMMARY>
  /// <PARAM name="ExceptionInfo"> The XML DOM object containing the exception information</PARAM>
  /// <PARAM name="ConfigSettings"> Configuration settings from the *.config file for this publisher. Requires the
  /// fileName configuration attribute within the *.config file entry.</PARAM>
  void IExceptionXmlPublisher.Publish(XmlDocument ExceptionInfo, NameValueCollection ConfigSettings)
  {
   OnPublish(ExceptionInfo,ConfigSettings);
  }

  /// <SUMMARY>
  /// Overrideable implementation of IExceptionXmlPublisher.Publish interface method.  Writes the
  /// exception to file, using a mutex to allow for concurrent write operations to be
  /// synchronized. 
  /// </SUMMARY>
  /// <PARAM name="ExceptionInfo"> The XML DOM object containing the exception information</PARAM>
  /// <PARAM name="ConfigSettings"> Configuration settings from the *.config file for this publisher.  Requires the
  /// fileName configuration attribute within the *.config file entry.</PARAM>
  protected virtual void OnPublish(XmlDocument exceptionInfo, NameValueCollection configSettings)
  {      
   string fileName;
   if (configSettings != null)
   {
    fileName = configSettings["fileName"];
   }
   else
   {
    fileName = @"C:\ErrorLog.xml";
   }
   
   Mutex mutex = new Mutex(false, "mymutex.blah.blah");
   if (!mutex.WaitOne())
   {
    //TODO:  What to do with the error if this block fails? 
    // For now, log to event log and swallow the error.
    EventLog log = new EventLog("ErrorLog");;
    try
    {     
     if(!EventLog.SourceExists("debugging"))
     {
      EventLog.CreateEventSource("debugging", "ErrorLog");
     }     
     log.Source = "debugging";
     log.WriteEntry("Unable to create mutex in ExceptionXmlPublisher.",System.Diagnostics.EventLogEntryType.Error);
     return;
    }
    finally
    {
     log.Dispose();     
    }
   }

   try
   {
    using (FileStream fs =  File.Open(fileName, FileMode.Append ,FileAccess.Write,FileShare.Write ) )
    {                           
     StreamWriter writer = new StreamWriter(fs);
     writer.Write(exceptionInfo.OuterXml);
     writer.Flush();
    }
   }
   finally
   {
    mutex.ReleaseMutex();
   }   
  }
 }
}

<Kirk />

  • I posted an alternative on my blog.
  • Nice job.

    The difference between implementations is that your solution overwrites the last entry (due to the use of FileMode.OpenOrCreate).

    I had actually considered making the log file well-formed when I wrote the post last year. The problem is that it would take up too many cycles to seek to the end of the stream minus the position of the </log> element while taking into account different encoding offsets.

    XmlReader can be used to read an XML fragment, or you can append a root node to the log XML document before reading it. In fact, this is what you have to do when using SqlCommand.ExecuteXmlReader using SQL Server's FOR XML clause because FOR XML does not necessarily return well-formed XML. I chose the approach of putting the burden on the reading application rather than put the burden on the logging implementation.
  • I don't overwrite the last entry, I overwrite the entire file. That probably takes even more cycles. :) I like your decision about putting the burden on the reading app. Thanks!!!
Page 1 of 1 (5 items)
Leave a Comment
  • Please add 7 and 2 and type the answer here:
  • Post
Translate This Page
Search
Archive
Archives