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.
EventLogEx log = new EventLogEx(
"MyCustomLog", ".", "EventLogExTest" );
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:
- Create an .MC file to define the message resource table (See MSDN for more
information)
- Use the message compiler to create the .RC and .BIN files from the .MC file:
mc filename.mc
- Use the resource compiler to create the .RES file:
rc -r -fo filename.res
filename.rc
- 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