Click here to Skip to main content
Click here to Skip to main content

Not So Simple Error Log

, 24 Apr 2005
Rate this:
Please Sign up or sign in to vote.
Remake of sSimple XML based Error Log, this time more powerful and with more features.
Sample Image - NotSoSimpleErrorLog.gif

Contents

Introduction

This article is a remake of Simple XML based Error Log. However in this release, I've made lot of changes so I'd rather come up with a new article instead of an update. The basic idea is the same, to log exceptions which have occurred in your application into an XML file. The major differences are:

  • All Exceptions are logged as derivate from base exception DNHCommonException from namespace DNH.Common. I'll write about it in detail later in this article.
  • ErrorLog provides more options in logging:
    • single file
    • single file per hour
    • single file per day
    • single file per exception
  • Actual code for logging is completely rewritten in order to improve performance and consume less memory. Again, I'll cover optimizations in this article.
  • ErrorLog comes with pre-created XSLT transformations.
  • ErrorLog is now thread safe.
  • Logging format is described via XML Schema.
  • Exception itself now provides actual formatting into XML.
  • DNH.Common.Errors provides means of error notification (will discuss about notification event and notification providers later).
  • ErrorLog has Error event.

As you can see, the main focus is on scalability and reasonable performance, so I believe this article can be useful.

What Can You Learn

This article provides some examples on how to:

  • effectively append into large XML files.
  • design plugin architecture (OK, I'd love some feedback here).
  • send mails using classes from System.Web.Mail.
  • transform XML file using XSLT support in System.Xml transformations.
  • effectively query XML file using classes from System.Xml.XPath namespace.
  • raise and handle events.

The Library

ErrorLog is part of my library DNH.Common. This library is everything but finished project, but don't worry. Most likely (90% chance) I won't change its interface with regard to logging solution presented here. The basic piece of library is DNH.Common.Dll. In this assembly, there is one very important class and interface. The class is called DNHCommonException and that interface is IOutputAsXml.

The IOutputAsXml Interface

As name of this interface says, classes implementing IOutputAsXml provide some kind of XML output. Point of this system is that you don't care about XML representation of these classes. You just cast them to IOutputAsXml. This is how it looks in C#:

/// <summary>
/// Provides XML output for objects implementing this interface.
/// </summary>
public interface IOutputAsXml
{
    /// <summary>
    /// Returns XML representation of this object.
    /// </summary>
    XmlDocument ToXml();
    
    /// <summary>
    /// Gets or sets XML Schema for result of ToXml() method.
    /// </summary>
    System.Xml.Schema.XmlSchema Schema{get;set;}
}

And this is a typical usage:

// MyObject.cs
class MyObject:IOutputAsXml
{
    // TODO: constructor etc..
    
    private string _value;
        
    public XmlDocument ToXml()
    {
        XmlDocument doc = new XmlDocument();
        XmlDeclaration declaration = 
            doc.CreateXmlDeclaration("1.0","UTF-8","yes");
        XmlElement root = 
            doc.CreateElement(null,"root",this.Schema.TargetNamespace);
        root.InnerText = this._value;
        doc.AppendChild(declaration);        
        doc.AppendChild(root);
        return doc;  
    }
}
// ThisUseMyObject.cs
...

XmlDocument doc = myObject.ToXml();
doc.Save("myObject.xml");
...

// myObject.xml
// assuming myObject._value had value "myValue"
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>myValue</root>

At this point, you can see that it's possible to call ToXml() for members, so you can easily represent compound objects in XML.

Difference Between IOutputAsXml and Serialization

In .NET the word Serialization means: "Serialization can be defined as the process of storing the state of an object instance to a storage medium. During this process, the public and private fields of the object and the name of the class, including the assembly containing the class, is converted to a stream of bytes, which is then written to a data stream. When the object is subsequently deserialized, an exact clone of the original object is created." On the other way, IOutputAsXml obligate implementers to provide *some* XML representation - it's not required nor intended to be an exact clone. Think about it as the XML alternative to generic object.ToString() method.

The DNHCommonException class

DNHCommonException derives from System.Exception and implements IOutpuAsXml interface. DNHCommonException provides readonly property DNHCommonExceptionType ExceptionType which returns member of DNHCommonExceptionType. Possible values and their meanings are:

/// <summary>
/// Represents types of exception severity.
/// </summary>
public enum DNHCommonExceptionType
{
    /// <summary>
    /// Identifies general errors, like IO/missing files, wrong format, timeouts. 
    ///This is default value.
    /// </summary>
    Error = 0,
    /// <summary>
    /// Identifies fatal errors, like null referece, etc.
    /// </summary>
    FatalError = 1,
    /// <summary>
    /// Identifies security errors, like insufficient permisson.
    /// </summary>
    SecurityError = 2,
    /// <summary>
    /// Identifies unhandled exception.
    /// </summary>
    UnhandledError = 3
}

You can specify the type of error in exception's constructor overload. Obviously, this information is included in actual log file.

XML Schema

You can find XML Schema for basic DNHCommon.DNHCommonException here.

The ErrorLog Class

This class implements actual writing to XML file. There are several "modes" of logging, and technique of writing XML sometimes differ. ErrorLog exposes two events: Error and Notification. The first event fires before ErrorLog logs exception, and second event fires before notification is sent.

Note: In the current implementation, both events fire before an exception is logged, but this may change in the future. The rule is that Error event fires before exception is logged and Notification event fires before notification is send (as I already wrote), but there is no "correct" order for Error and Notification events (and for actual logging and sending notification, too).

What happens when it comes to saving error information into file depends on logging mode. When in oneExceptionFile mode, saving is done this way:

 // create new document
 XmlDocument doc = new XmlDocument();
 doc.LoadXml(this.ToXml().OuterXml);
 XmlDocumentFragment frag = doc.CreateDocumentFragment();
 frag.InnerXml = e.ToXml2().OuterXml;
 doc.DocumentElement.AppendChild(frag);
 doc.Save(tempLogName);

It can't be more straightforward. One entry is a relatively small piece of XML, so there is no big deal with using XmlDocument.

A different situation is when we use one of the "appending" modes. In this scenario, XML files grows and you could easily get files about few MB or larger. So it's time to use the different approach. In my code you can find this method:

private void Append(string path, DNHCommonException e)
{
     StreamWriter sw = File.AppendText(path);
     XmlTextWriter xtw =  new XmlTextWriter(sw);
     xtw.WriteRaw(e.ToXml2().OuterXml);
     xtw.Close();
}

Basically, it appends XML into plain text file... that XML doesn't even have root element, so it can't be used directly. Instead, I use the technique described in Efficient Techniques for Modifying Large XML Files. Point is in using IO API, which is much faster than using XmlReader/XmlWriter or (even worse) XmlDocument. Now there is question: OK, so we have XML-like plain text file. How to make "regular" XML file from it, with all advantages of XSLT, etc.? Answer is - entity. Together with text file, there is the XML file like this:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!doctype errorlog[<!ENTITY log SYSTEM "log.txt">]>

As you can see in document type definition, &log; is reference to the external entity, in this case it's the text file log.txt. When XML reader which is able to expand entities (for example XmlValidatingReader ) reads our XML file, it "includes" log.txt into our XML file. So, in result, when you call XSTL transformation on it (as shown later in this article), result contains ALL markup - even from log.txt.

The NotificationProviderBase Class

NotificationProviderBase is base class (surprisingly) for notification providers. It has one function:

public abstract void Send(DNHCommonException ex);

You call this function when you want to send notification of error (e.g. you want to send email to administrator and tell him that access to DB failed or something like that). Rules when, how and who to send notification are described in configuration file. DNH.Common.Errors comes with one implementation of NotificationProviderBase, EmailNotificationProvider is in assembly DNH.Common.Errors.Providers.EmailNotificationProvider.dll. You can look how it's written (source is included with library). In fact override for Send() is very simple:

/// <summary>
/// Sends notification.
/// </summary>    
public override void Send(DNHCommonException ex)
{                          
    if(this._smtpServer != null)
    {
        SmtpMail.SmtpServer = this._smtpServer;
    }
    SmtpMail.Send(message);
}

The ErrorLogConfig Class

Class ErrorLogConfig, as it's name says, provides API to library's configuration file. There are classes from System.Configuration namespace, but they don't meet my needs (or I don't know/understand them enough?), so I wrote ErrorLogConfig class in order to provide API I needed. It internally uses System.Xml.XPath.XPathDocument for reading configuration file. Speaking about XPath document, there is another rule for efficient use of XML in the .NET framework: Don't use XmlDocument when you don't want to modify your XML document. XmlDocument is relatively a heavy thing and it is memory/performance overkill to use it for XPath-quering (selecting nodes) in XML file. Use XPathDocument instead.

Config file format

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <!-- 
        List of exceptions.
        Add new exception here.
    -->
    <exceptions>
        <exception type="DNH.Common.DNHCommonException">
            <target name="boss" />
            <target name="admin" />
        </exception>
     </exceptions>
     
     <!--
        List of notifications.
        Add new notification here.
     -->
     <notifications>     
        <notification target="admin@example.org" nick="admin" >
             <provider name="Email" />             
        </notification>
        <notification target="boss@contozo.com" nick ="boss" >
             <provider name="Email" />
        </notification>
     </notifications>
     
     <!-- 
        List of notification providers.
        Add new notification provider here.
     -->
     <providers>
          <provider name="DNH.Common.Errors.Providers.EmailNotificationProvider" 
                nick="Email" />          
     </providers>
     
</configuration>

I hope this structure is intuitive enough. Typically, there is a long list of exceptions and much shorter list of targets and providers.

The ErrorLogFormatter Class

This is a helper class, it has one method:

public static void FormatErrorLog(string inFile, string outFile, 
                                              String StyleSheetURI)

This method applies XSLT transformation (StyleSheetURL parameter is URL to .xslt file) to file inFile and saves result as outFile. There are two predefined .xslt files in demo, log2html.xslt and log2txt.xslt. First one formats log's XML into nice HTML, second one into old-school plain text format. Of course, if you know XSLT, you can provide your own formatting. Implementation looks like this:

/// <summary>
/// Formats error log with given xslt transformation and saves result in file.
/// </summary>
/// <param name="inFile">Input XML file.</param>
/// <param name="outFile">Output file.</param>
/// <param name="StyleSheetURI">URI to stylesheet.</param>
public static void FormatErrorLog(string inFile, string outFile, String StyleSheetURI)
{
    XslTransform transform = new XslTransform();            
    transform.Load(StyleSheetURI);
    XmlWriter outWriter = new XmlTextWriter(outFile,System.Text.Encoding.UTF8);
    XmlDocument myDoc = new XmlDocument();
    XmlValidatingReader vr = new XmlValidatingReader(new XmlTextReader(inFile));
    vr.EntityHandling = EntityHandling.ExpandEntities;
    vr.ValidationType = ValidationType.None;                        
    myDoc.Load(vr);
    XmlUrlResolver myResolver = new XmlUrlResolver();            
    myDoc.XmlResolver = myResolver;
    transform.Transform(myDoc,null,outWriter,myResolver);
    outWriter.Close();
}

Example

Now last thing: how to actually use ErrorLog? You can declare/create new instance of ErrorLog like:

public ErrorLog log;
log = new ErrorLog("logs/log.xml");

Than you can call ErrorLog.SaveExceptionToLog(Exception ex) method in try-catch block.

try
{            
   InfinityRulez();                    
}
catch (Exception ex)
{
   log.SaveExceptionToLog(ex);
}

If you want to do some custom action (e.g display error page) when ErrorLog.SaveExceptionToLog() method is called, you can register event Error like this:

private void InitializeComponent()
{
   this.log.Error += new ErrorLogErrorEventHandler(log_Error);
   ...

Then you only need to write body of log_Error handler... You can register/handle Notification event same way.

For guidelines about throwing and handling exceptions, please search CodeProject, or MSDN, or search with Google or other favourite search engines by your choice. There is a lot to say about this topic, it would be material for a whole new article. So I just skip this part and pray that you (reader) will investigate it by yourself!

Demo Application

I've included demo application, so you can quickly see my solution in action. User interface and whole application is very simple and it has only one purpose - to show how logs looks, that it is thread safe etc... However there are some hard-coded paths so please believe me that folders logs, XSLT and their content should be where it is now... Of course, only in this sample! In real use you can change all paths as you wish. However configuration file DNH.Common.Errors.Config must be in same folder as assembly DNH.Common.Errors.Dll. Also if you want to use notification providers (plugins) their location must be same as you write in .config file. That's all, have fun. I am looking forward to your bug reports, ideas and improvements!

TODO

There is of course a lot to do... :

[TODOs from Simple XML based Error Log]

  • case when more exceptions occurs in same time should be handled. Done.
  • add more informations (like infos about currently logged users in your app). Done.
  • manipulating with XML could be done using XmlDocument or XmlDocumentFragment. It's more clear. NO!
  • generated XML is described by DTD. Expect use of XML Schema in next release. Done.
  • this one is difficult: automatic analysis of occured errors. but it would be great Smile | :)

[Current TODOs]

  • Implement some kind of friendly error messages for user (yes, like in User Friendly Exception Handling).
  • Improve project documentation.
  • Further improve performance.

References

History

  • 24.4.2005

    Article first posted to CodeProject.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

DavidNohejl
Student Charles University in Prague
Czech Republic Czech Republic
My real name is David Nohejl, aka DNH. I am from Prague, Czech Republic. I am interested in many aspects of programming (databases, AI, algorithms...). My favourite programming language is C#, favourite IDE is, of course, VS.NET Smile | :) . I love XML and all the things around it... Other interest than programming are science (math, physics, biology, psychology, paleontology) and basketball. Graduated Faculty of Mathematics and Physics, Charles University in Prague as BSc. Working as programmer in company Unlimited ltd.. Doing cool stuff with C#, WPF, Javascript.
Follow on   Twitter

Comments and Discussions

 
GeneralExcellent work! PinmemberPortatofe2-Oct-08 8:45 
GeneralCross-thread operation not valid [modified] Pinmemberdepmoddima8-Nov-07 21:32 
GeneralSchema Pinmemberchatterad1-Oct-07 7:48 
GeneralRe: Schema Pinmemberdnh1-Oct-07 8:59 
GeneralRe: Schema Pinmemberchatterad1-Oct-07 9:06 
GeneralRe: Schema Pinmemberdnh1-Oct-07 10:01 
GeneralInvalid cast exception thrown if original exception has an InnerException PinmemberPaul Czy26-May-05 9:21 
GeneralRe: Invalid cast exception thrown if original exception has an InnerException Pinmemberdnh26-May-05 10:04 
GeneralGreat code PinmemberFábio Batista28-Apr-05 15:30 
GeneralNice... PinmemberWiebe Tijsma25-Apr-05 4:24 
GeneralRe: Nice... Pinmemberdnh25-Apr-05 6:33 
GeneralTricky demo PinmemberTommi G24-Apr-05 4:14 
GeneralRe: Tricky demo Pinmemberdnh24-Apr-05 4:23 
GeneralRe: Tricky demo [Updated] Pinmemberdnh24-Apr-05 5:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140709.1 | Last Updated 24 Apr 2005
Article Copyright 2005 by DavidNohejl
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid