Introduction
Ideally, a good software application should never, or occasionally, come up with error conditions or exceptions. But as we all know, this is far from reality. So, when an exception does occur, a good application should just not log plain error messages, but also provide troubleshooting information and detailed information about the error in terms of its source and causes.
The Windows Event Viewer has always been the most suitable place to log error messages generated by applications. This article explains how we can use the .NET Event Logging API to effectively log error information. The article also touches upon some practices for effective error message management, like maintaining localized error messages and troubleshooting hyperlinks. We shall begin with some basics of event logging like event types, log-files, etc, and then cover the implementation aspects with respect to .NET.
Event types
There are basically five types of events that can be logged. Each event is of a particular type, and an error logging application indicates the type of event when it reports one. The Event Viewer uses this type, to determine the icon to display in the list view of the log.
Event Type | Description |
Information | This indicates a significant, successful operation. For example, an event indicating that a service has started. |
Warning | Warning events indicate problems that are not immediately significant, but those that could cause problems in the future. Resource consumption is a good example for a warning event. |
Error | Error events indicate significant problems that the user should know about. Error events usually indicate a loss of functionality or data. |
Failure Audit | Failure audit events are security events that occur when an audited access attempt fails. A failed attempt to open a file (due to lack of permissions) is an example for a failure audit event. |
Success Audit | Failure audit events are security events that occur when an audited access attempt fails. A failed attempt to open a file (due to lack of permissions) is an example for a failure audit event |
Event logging elements
The following are the major elements that are used when logging events:
Log-files
log-file is the place where the all event entries are made. The event logging service uses information stored in the EventLog
registry key. The EventLog
key (shown in Figure.1) contains several sub-keys called logfiles
. log-file information in the registry is used to locate resources that the event logging service needs when an application writes to and reads from the event log. The default log-files are Application, Security, and System.
Figure 1: EventLog
key in the registry
Applications and services use the Application log-file, whereas device drivers use the System log-file. The system generates success and failure audit events in the Security log-file when auditing is turned on. Applications can also create custom log-files by adding an entry to the EventLog
registry key (This can be done programmatically as well). These logs appear in the Event Viewer with the default log-files. It is a good practice to have a separate log-file in the event viewer for your application, as this makes isolating errors generated by your application easier. Also, each log-file will be an independently manageable unit. For instance, we can control the size of the log-file, attach ACLs for security purposes etc.
Event log records
Information about each event is stored in the event log in an event log record. The event log record includes time, type, source, and category information of each event.
Event sources
The event source is the name of the software component (or a module of the application) that logs the event. It is often the name of the application, or the name of a subcomponent of the application, if the application is large. While applications and services should add their names to the Application log-file, or a custom log-file, the device drivers should add their names to the System log-file. The Security log-file is for system use only.
Figure 2: Registry Structure: Event Source
Each log-file contains sub-keys for event sources (as shown in Fig 2).Each event source contains information specific to the software that logs the events. The following table shows the various registry keys that can be configured for an event source:
Registry Value | Description |
CategoryCount | Specifies the number of event categories supported. |
CategoryMessageFile | Specifies the path for the category message file. A category message file contains language-dependent strings that describe the categories. |
DisplayNameFile | Specifies the file that stores the localized name of the event log. The name stored in the specified file appears as the log name in the Event Viewer. If this entry does not appear in the registry for an event log, the Event Viewer displays the name of the registry sub key as the log name. |
DisplayNameID | Specifies the message identification number of the log name string. This number indicates the message in which the localized display name appears. |
EventMessageFile | Specifies the path for the event message file. You can specify multiple files, each separated by semicolons. An event message file contains language-dependent strings that describe the events. |
ParameterMessageFile | Specifies the path for the parameter message file. A parameter message file contains language-independent strings that are to be inserted into the event description strings. |
TypesSupported | Specifies a bit mask of supported types. |
Basically, event sources can be used to classify error messages as required by the application. For example, you can have sources like reporting, calculations, user interface, etc for an accounting application.
We shall see how we configure these values later in the article.
Event categories
Categories help organize events so that we can filter them in the Event Viewer. Each event source can define its own numbered categories, and the text strings to which they are mapped. The categories must be numbered consecutively, beginning with the number one. The total number of categories is stored in the CategoryCount
key for the event source. Categories can be stored in a separate message file, or in a file that contains messages of other types. We shall talk more about creating categories under the section titled Message Files.
Event identifiers
Event identifiers identify a particular event uniquely. Each event source can define its own numbered events, and the description strings to which they are mapped. Event viewers present these descriptions strings to the user.
Message files
Message files are text files that contain information about the various messages and categories that applications want to support. These text files are then compiled as resource DLLs. Resource DLLs are small and fast when compared to normal DLLs. The advantage of these resource DLLs is that we can have messages written in multiple languages. By using these DLLs, we can have a truly localized application with localized error messages. Each event source should register message files that contain description strings for each event identifier, event category, and parameters. These files are registered in the EventMessageFile
, CategoryMessageFile
, and ParameterMessageFile
registry values for the event source. We can create a single message file that contains descriptions for the event identifiers, categories, and parameters, or create three separate message files. Several applications can share the same message file.
Implementation
This article so far discussed the concepts behind event logging. Now, let us consider the implementation aspects with respect to .NET.
Creating an Event log-file
There are two ways to create an event log-file:
- Manually creating the registry entries
This can be done by adding an entry under HKEY_LOCAL_MACHINE\SYSTEM\Services\EventLog
.
- Programmatically
To create log-files, event sources and also to log events in the event log we use the EventLog
class defined in the System.Diagnostics
namespace.
If Not EventLog.SourceExists("MySource", "MyServer") Then
EventLog.CreateEventSource("MySource", "MyApp", "MyServer")
End If
In the code piece shown above, we are creating an event source called MySource
under MyApp log-file on the machine MyServer if one does not already exist.
Creating event sources
Event sources can be created manually or programmatically (as shown earlier), similar to Event log-files. If you are creating sources manually, then you have to create several keys such as CategoryMessageFile
and EventMessageFile
under the source. The snapshot given below shows the registry entries after the log-file and event sources are created.
Figure 3: Event source registry entries
In Figure 3, we have created a log-file for the application called MyApp. Under this log-file, there are two sources MySource1 and MySource2. In the example shown above, Msg.dll in the temp directory is used to obtain both messages and category names (as provided for the CategoryMessageFile
and EventMessageFile
entries). The event source has three categories (CategoryCount
is set to 3), and all event types are supported (TypesSupported
is set to 7).
Message files
A detailed explanation of message text file syntax is beyond the scope of this document.
Given below is a sample message text file:
;//**************Category Definitions************
MessageId=1
Facility=Application
Severity=Success
SymbolicName=CAT1
Language=English
MyCategory1
.
MessageId=2
Facility=Application
Severity=Success
SymbolicName=CAT2
Language=English
MyCategory2
.
;//***********Event Definitions**************
MessageId=1000
Severity=Success
SymbolicName=MSG1
Language=English
My Error Message
.
MessageId=2000
Severity=Success
SymbolicName=GENERIC
Language=English
%1
.
Note: Message files can be in Unicode to support messages written in any language. In the example given above, messages are written in English. The categories and messages have also been provided in the same file.
Creating Message DLLs
We need to compile the message file to a resource-only DLL. The following steps explain the conversion process:
- Create a .mc file to define the message resource table. This file has all the error messages and categories of an application. Use the message compiler to create .rc and .bin files from the .mc file.
mc filename.mc.
- Use the resource compiler to create a .res file.
rc -r -fo filename.res filename.rc
- Use the linker to create a .dll file.
link -dll -noentry -out:filename.dll filename.res
Logging an error
To log an event into the event log, the WriteEntry
method of the EventLog
class can be used. Given below is a code snippet detailing how a message can be logged:
Dim objEventLog As New EventLog()
If Not objEventLog.SourceExists("MySource1") Then
objEventLog.CreateEventSource("MySource1","MyApp")
End If
objEventLog.Source = "MySource1"
objEventLog.WriteEntry("", EventLogEntryType.Error, 1000, CShort(1))
For the code sample above, refer to the message text file shown earlier.
Note a couple of things here. We are passing the EventID
parameter as 1000. This corresponds to the message "My Error Message" in the message text file. This will be displayed as the message in the event log. Also, we are passing the CategoryID
as 1. This corresponds to the category of MyCategory1. Note that I am not passing any error message as this is picked up from the message file. Figure 4 illustrates how the event log entry would look like when the above code snippet is executed:
Figure 4: An event log entry with the EventID
and Category
We now have our own node in the event viewer. By looking at the event log, the user gets a fair amount of information like the source of the error and its category. Not only that, this makes filtering messages easier as we now have our own Sources and Categories. Also, by using the same method, we have effectively segregated error messages from the application code itself. This would help us maintain a uniform standard for error messages across applications, and also take us a step closer to localization.
Providing links in error messages
Another good feature the Event Viewer provides is that of hyperlinks in the error messages, which can be linked to a web page detailing more about the error. This would help an end user understand and troubleshot the error. For example, the message given below has a link to a web page:
Figure 5: Troubleshooting Link given with an Error Message
On clicking this link, we would be prompted with a dialog box as shown below:
Figure 6: Posting Error Information to a designated place
When the user confirms to send this information, a web page can be programmed to receive this information, and display troubleshooting information to the user based on the EventID
, Category
, and Source
sent from the client (This information is sent as query string parameters).
Conclusion
The event viewer definitely provides a wealth of features through which error messages can be effectively logged and tracked. By exploiting these features, we would help end users to understand the errors and their sources better, and this in turn can simplify things for the support group.
A note about the source files
Included with this article is a simple VB.NET project which just shows how the WriteEntry
method of the EventLog
class can be used. The zip also includes a .reg file which should be run to create the custom log-file and to register the event sources. Note that some paths are hard coded in the .reg file. So, change them accordingly. Finally, a batch file Compile.bat is included to compile the .mc file to create a message DLL.