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).
As you can see, the main focus is on scalability and reasonable performance, so I believe this article can be useful.
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
- transform XML file using XSLT support in
- effectively query XML file using classes from
- raise and handle events.
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
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#:
public interface IOutputAsXml
And this is a typical usage:
private string _value;
public XmlDocument ToXml()
XmlDocument doc = new XmlDocument();
XmlDeclaration declaration =
XmlElement root =
root.InnerText = this._value;
XmlDocument doc = myObject.ToXml();
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
At this point, you can see that it's possible to call
ToXml() for members, so you can easily represent compound objects in XML.
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
DNHCommonException derives from
System.Exception and implements
DNHCommonException provides readonly property
DNHCommonExceptionType ExceptionType which returns member of
DNHCommonExceptionType. Possible values and their meanings are:
public enum DNHCommonExceptionType
Error = 0,
FatalError = 1,
SecurityError = 2,
UnhandledError = 3
You can specify the type of error in exception's constructor overload. Obviously, this information is included in actual log file.
You can find XML Schema for basic
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:
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:
XmlDocument doc = new XmlDocument();
XmlDocumentFragment frag = doc.CreateDocumentFragment();
frag.InnerXml = e.ToXml2().OuterXml;
It can't be more straightforward. One entry is a relatively small piece of XML, so there is no big deal with using
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);
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
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:
<!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.
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
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:
public override void Send(DNHCommonException ex)
if(this._smtpServer != null)
SmtpMail.SmtpServer = this._smtpServer;
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
<target name="boss" />
<target name="admin" />
<notification target="email@example.com" nick="admin" >
<provider name="Email" />
<notification target="firstname.lastname@example.org" nick ="boss" >
<provider name="Email" />
I hope this structure is intuitive enough. Typically, there is a long list of exceptions and much shorter list of targets and providers.
This is a helper class, it has one method:
public static void FormatErrorLog(string inFile, string outFile,
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:
public static void FormatErrorLog(string inFile, string outFile, String StyleSheetURI)
XslTransform transform = new XslTransform();
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;
XmlUrlResolver myResolver = new XmlUrlResolver();
myDoc.XmlResolver = myResolver;
Now last thing: how to actually use
ErrorLog? You can declare/create new instance of
public ErrorLog log;
log = new ErrorLog("logs/log.xml");
Than you can call
ErrorLog.SaveExceptionToLog(Exception ex) method in
catch (Exception 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!
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!
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 NO!
XmlDocumentFragment. It's more clear.
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 :)
- Implement some kind of friendly error messages for user (yes, like in User Friendly Exception Handling).
- Improve project documentation.
- Further improve performance.