Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Enhanced EventLog writing for .NET Applications

0.00/5 (No votes)
11 Nov 2002 1  
A set of C# classes that work around issues with the standard EventLog object

Introduction

The EventLog class provided with the .NET framework allows easy access to the NT event log subsystem but has some implementation "features" that restrict it's usefulness (in fact, it is probably best described as a leaky abstraction).

A good description of the issues (summarised below) is in section 2.5.1 of "Developing Applications with VisualStudio .NET" by Richard Grimes (ISBN 0-201-70852-3).

The EventLogEx class presented here works around these issues and enables applications to write to the event log in a manner similar to the current direct API calls (For reading, the existing framework class works fine).

Whilst the code is fairly generic, it was created to support a project I am working on and, as such, it may have some design features that seem strange or just plain wrong in the "real world" -- please feel free pull the code apart and let me know of any issues/comments/solutions!

Also included is a simple C# command-line utility that can be used to convert the .h file created by the Microsoft Message Compiler (MC) into a C# class (see The MCConvert Utility) that can be included into solutions using this code.

Event logging pre-.NET

Writing to the log

When you access the event log using the standard NT API calls, the system stores a structure that contains (amongst other things) the message ID and any replacement strings ("inserts") for the message -- but it does not store the message text itself.

Reading from the log

When you read an entry from an event log, the system reads the stored message ID and replacement strings, gets the text of the message for the current locale from a MESSAGETABLE resource contained within the file specified in the EventMessageFile key in the registry, inserts the replacement strings, and returns you the formatted string.

As well as keeping the log file small (which improves performance when accessing the event log on a remote machine), just storing the message ID and replacement strings also means that the same message can be viewed in different languages as long as the client:

  • Has the file installed that contains that locales MESSAGETABLE
  • The local registry has been configured to tell NT where to find it

The file containing the messages only has to be installed on the machine that is doing the reading and does not have to exist on the one that is doing the writing or the one that holds the log (they can all be different machines).

Event logging with .NET

Under .NET, message sources are registered with the EventMessageFile value always set to EventLogMessages.dll, which is installed in the GAC. This file has 65,535 entries, each of which contain a single string: %1

In other words, for every possible event ID the entire format string is a placeholder that takes a single replacement string -- which is always the message that you pass to EventLog.WriteEntry()

The main drawbacks with this approach are:

  • You have the responsibility of choosing the locale that should be used to format the message before writing it to the log and so all clients have to view the message in the same language
  • The log file is larger than necessary as it has to hold the full formatted string rather than just the message ID and replacement strings
  • If you want to view the entries written to a remote log on that machine, it must have the .NET runtime installed and the EventLogMessages.dll file registered in the remote computer's GAC.

Event logging with EventLogEx

The EventLogEx class overcomes these issues and allows you to log messages from managed code with read-time formatting of the messages based on the client locale (i.e. it exposes the native API calls as a managed class).

Class Overview

For full API information, please refer to the NDoc generated documentation included in the .ZIP file (EventLogEx.chm)

The EventLogEx class

The event-logging service uses the information stored in the EventLog registry key (HKLM\System\CurrentControlSet\Services\EventLog)

The EventLog subkeys are called logfiles and are used to locate resources that the event logging service needs when an application writes to or reads from an event log. The default logfiles are Application, Security, and System and you can also create custom logfiles.

Each subkey has configuration values specific to that logfile which can be read or set using the relevant EventLogEx properties.

The EventSource class

Each logfile entry contains one or more subkeys called event sources which are the names of the software components that log events to that logfile (normally this is the name of the application or the name of a subcomponent of the application, if the application is large).

Event sources are stored under the HKLM\System\CurrentControlSet\Services\EventLog\LogName\AppName key and contain information specific to the software that will be logging the events, and which can be read or written using the relevant EventSource properties.

NOTE: You cannot construct an instance of the EventSource class directly - instead use the Source property of an existing instance of the EventLogEx class.

The MessageFile properties

The EventSource object exposes the *MessageFile properties in two versions -- Local and Remote (e.g. LocalEventMessageFile and RemoteEventMessageFile).

This is necessary because, to be able to view the text of events stored in a log on a remote machine, the local registry must have an event source entry that specifies the location of file that contains the MESSAGETABLE resources.

The remote machine must also have a similar one if the event is being written to a custom log file, otherwise the NT event service defaults to writing the messages to the Application log (if it is being written to the Application log the entry is optional but recommended).

The remote entry will also allow the events to be viewed in its Event Viewer if it also has the MESSAGETABLE resource files installed -- but these are likely to be in a different directory than on the local machine and so the Remote* properties allow you to provide different paths for the local and remote paths (if the source is local, calling the Remote* versions has no effect).

Using the code

The EventLogEx class should be used as a replacement for the standard EventLog only when writing events - when reading events, the existing EventLog class should be used.

The main difference with the class is that, instead of using the WriteEntry() method and passing it a string message, you use ReportEvent() and pass it a message ID.

//

// Open a source on the local machine and then associate 

// it with a custom log called "MyCustomLog"

//

EventLogEx log = new EventLogEx( 
    "MyCustomLog", ".", "EventLogExTest" );

// Write a test message with some extra data

byte[] errorInfo = Encoding.ASCII.GetBytes( 
    new string("Some useful additional data") );

log.ReportEvent( EventLogEntryType.Warning, 0,
    (uint )EventLogExTestMessages.MSG_WITH_THREE_PARAM,
    new string[] { "The data", "below is", "the error" }, 
    errorInfo );

Points of Interest

.NET Registry issue

According to the documentation, any registered paths (e.g. to the log file and message files) should be of type REG_SZ_EXPAND but you cannot create an entry of this type using the standard .NET Registry classes (you can only create REG_SZ or REG_MULTI_SZ types).

This most notably affects the path to the actual log file as it should be under the system directory which, on a remote machine, may not be the same as the local one (so you can't use just use the Environment.SystemDirectory value).

Using "%SystemRoot%" in the path without setting the type to REG_SZ_EXPAND does not work correctly as it will not be resolved when read.

The solution that I have used is to read the "SystemRoot" value from the (possibly remote) registry and then use that to build the path up.

The MCConvert Utility

VisualStudio.NET does not contain an editor for MESSAGETABLE resources (although I am working on one) so you currently need to create the message files in the same way as pre-.NET (i.e. using the Microsoft message compiler mc.exe).

You can create one message file that contains descriptions for the event identifiers, categories, and parameters, or create three separate message files and several applications can share the same message file.

You should typically create message files as resource-only DLLs as they are smaller and faster to load than ordinary DLLs:

  1. Create an .MC file to define the message resource table (See MSDN for more information)
  2. Use the message compiler to create the .RC and .BIN files from the .MC file: mc filename.mc
  3. Use the resource compiler to create the .RES file: rc -r -fo filename.res filename.rc
  4. Use the linker to create the .DLL file: link -dll -noentry -out:filename.dll filename.res

To simplify the use of the MC generated .h file from within C# projects, a message converter tool is included in the .zip file. This can be used to convert the .h file into a C# file that contains a single public class called MessageIds that contains the message IDs as a set of public const uint values.

This utility is simple to use: mcconvert filename.h

This will generate a file called filenameMessages.cs that consists of a namespace called filenameMessages which contains a single MessageIds class, which in turn exposes the public const uint message IDs which are named the same as the #define entries in .h file and also the message text.

This file can then be included in an existing solution and the IDs used in the calls to the ReportEvent method of the EventLogEx class.

Note: The messages IDs are declared as public const uint values rather than a (to some people) more natural enumeration so that they can be passed directly to the ReportEvent method without the need to add a (uint) cast each time (and ReportEvent can't be easily modified to take an enumeration as it will have a different type for each .h file).

The EventLogExTest Program

The .zip file includes a simple test program for the EventLogEx class.

To build it, you should first run the "BuildMcDll.cmd" batch file in the EventLogExTest subdirectory which generates the .h/.cs files used by the program and also compiles the Test.Messages.dll file and copies it to the bin\debug directory so that the test application can locate it.

History

  • November 12, 2002 - Initial posting

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