There are different ways of storing application progress and event data. Custom log files are easy to manage, WMI is trendy and powerful, and of course there is no shortage of relational databases ready to accept your data. Another alternative is a method that's been available since Windows NT: the humble event log.
Using event log to store custom application specific event data presents some challenges. An event log entry has a small number of discrete properties that may not be able to cover your needs. There are a couple of numeric variables, a string variable or two, and a byte array that can be bent to hold your data. You can store XML in the message field of event log, but that prevents people from using the event viewer MMC snap-in and interpreting it visually.
You probably use the intrinsic data in the event log for the following reasons:
- The data store is ubiquitous on Windows machines.
- The information is network accessible if permissions are configured correctly.
- There is an existing viewer GUI, with which even non-developers are familiar.
- It is easily programmed with the objects exposed in the .NET Framework.
On the other hand, event log entries are immutable. Your data will remain as it was written originally in the log until the entire file is purged. More annoyingly, entries are not searchable except via the old fashioned, brute-force-linear-search way.
Keeping these limitations aside, I have found the ability to store objects in the event log helpful. In this article, I'll show you how to use the raw data of an entry and .NET serialization techniques to write and read strongly typed object data using the Windows application event log, allowing you to add arbitrary amounts of easily parsed data to the event log entries.
You can get the best of both worlds: something textual for interactive users to view, and a place to store complex data that can be read programmatically.
In this contrived example, the data we'll be persisting to the event log is trivial additional log information. This sort of data would fit perfectly into the existing
EventLogEntry object, but I'm keeping it simple and doing it this way for illustrative purposes. The
AppAlert object has two properties, an
Int32 ID and a
string name, and is decorated with the
[Serializable] attribute. You can see the full source code in the sample project.
The serialization and the event writing code is short and easy to read. It is implemented in the sample project's
cmdSave_Click event handler:
AppAlert appAlertData =
new AppAlert(Int32.Parse(txtID.Text), txtName.Text);
MemoryStream appAlertStream = new MemoryStream();
IFormatter appAlertFormatter = new BinaryFormatter();
byte serializedAppAlertData = appAlertStream.ToArray();
"Another app alert entered with an ID of " + txtID.Text,
Basically, you're serializing the state of the new
appAlertData object to a
byte array in memory, which you're then passing as a parameter to the
WriteEntry method of the
EventLog object. Serialization traditionally occurs directly to a physical media, so the stream would ordinarily be a
BinaryWriter or something of that sort, but you're doing it straight into the memory here since the event log APIs will take care of persistence to media.
You can get a glimpse of the stored data in the following snapshot of an event log entry:
Now that you can add custom objects to the event log, you have to know how to read them. In the sample project, clicking the "Refresh" button causes the application to load the Application event log into an array and iterate over the items. When it finds an event log entry with the
Source property of "
AppAlertData", it adds the
EventLogEntry object into the
Items collection. The
DrawMode property of the
ListBox is set to
OwnerDrawFixed, meaning you have to write the item rendering code, but don't have to worry about calculating the height of the item.
The deserialization code in the
Item_Draw event is as straightforward as the serialization code:
EventLogEntry ele = (EventLogEntry)lstAppAlerts.Items[e.Index];
MemoryStream appAlertStream = new MemoryStream(ele.Data);
IFormatter appAlertFormatter = new BinaryFormatter();
AppAlert appAlert =
Once that's complete, the
appAlert variable contains the strongly typed object originally stored there, and can be rendered into the
ListBox item. The rendering code is found in the sample project.
I've shown you how you can write information to the event log that doesn't clutter the GUI. You can use this information to extend the usefulness of the event log, and maybe avoid having to implement a custom logging scheme. I presented this as a technique, rather than as a library or framework, since everyone's object model is so different and there are only about 8 lines of code that will be common between implementations.
Points of interest
In the sample project, I use binary formatting. The alternative is SOAP formatting. I commented out two lines in the project (and added a reference to
System.Runtime.Serialization.Formatters.Soap that's irrelevant to the project as it is) that you can uncomment to examine this feature. SOAP formatting is much more verbose than binary (668 bytes vs. 156 bytes for the "2" "App ended." data), and doesn't provide any additional benefits except being easier to parse manually in code, and cognitively while examining the event log GUI. Both of these "advantages" to SOAP serialization are pretty marginal in this case, though, especially since the viewer snap-in prevents you from reading more than 4 lines of the SOAP message at a time. If you're using the framework tools to do your work, there's no reason to avoid using binary formatting.
I have not rigorously tested the impact on performance of using this technique. On my Pentium 4 development laptop, the entire event log data store of 28,000 odd entries is queried in 2 seconds, and there is no noticeable lag in navigating or opening the event log GUI. Having said that, it's probably best to not go hog-wild and save huge objects frequently.
Another technique you could use would be to create an event log specifically for your event types, which would eliminate the need to scan irrelevant entries. I admit it's clumsy to look through all the items in the event log for a string property, as I did it in the sample project. I considered showing that here, but decided against it to keep this simple and straightforward. More information about doing this is available in this excellent CodeProject article.
- Oct 30th, 2005 - Initial revision.