Features


A Logging Routine for Windows NT Events

David Hooker

Logging errors is an important but tedious task. It helps to stylize how you do it.


As a programmer who provides technical support and administration for his own programs, I am especially motivated to provide the clearest possible error messages in my code. Within Windows NT, the event log — with its built-in tools, utilities, and third-party support — is the standard for logging application errors. I found myself needing a single subroutine for writing to the event log that was both simple and reusable. I was able to accomplish this by using a subset of the event logging API and by borrowing an idea from printf.

Event Log Sources

An event log source contains message templates, which are the localized text of error messages. Listing 1, msg.mc, shows a message control file, which contains message templates. This file is compiled by the Microsoft message compiler (mc.exe) into a resource script, which in turn is compiled and bound to executable code, typically a resource DLL or a message DLL. The message compiler also produces a header file (not shown) containing one event identifer per message template.

Listing 2, Instsrc.cpp, shows the InstallEventLogSource routine for entering a message DLL into the registry. The first parameter to the routine is a registry entry, which is used to refer to the event log source. The second parameter is the path to the message DLL. This path may contain environment strings consistent with the REG_EXPAND_SZ registry data type. Note that what InstallEventLogSource writes to the registry is only a subset of the options available for message DLLs.

Emulating printf

Some of the message templates in Listing 1 contain replaceable parameters, such as %1, %2, and so on. These parameters are written to the event log as strings. Having a variable number of replaceable parameters that need to be formatted from a variety of data types suggests the idea of a printf analog for the event log. The function I developed has the following prototype:

DWORD
LogEvent(LPCSTR pszEventSource,
    DWORD dwEventID,
    LPCSTR pszFormat=NULL, ...);

The first three parameters of LogEvent correspond to the first parameter of printf. The ellipses in the parameter list indicate that LogEvent uses a variable argument list, which is managed by the C va_list feature. The first parameter is the event log source, which is also the first parameter to InstallEventLogSource. The second parameter is an event id in the header file emitted by mc.exe. If there are no replaceable parameters in a message template for the event id, then the third parameter, pszFormat, should be NULL. Otherwise, given that my formatting needs were more modest than those provided by printf, I allow one character in pszFormat per format specification per va_list parameter. These format specifications are as follows (they're case-insensitive):

d — The va_list parameter is a 32-bit integer and is formatted as such.

x — The va_list parameter is a 32-bit integer, and is formatted as a hexadecimal number, preceeded by "0x".

s — The va_list parameter is a pointer to a nul-terminated string, and is written to the event log as is.

m — The va_list parameter is an error number for which system error text, defaulting to an empty string, is retrieved.

See Listing 3, CopyFile.cpp, for concrete examples of how LogEvent is called.

Inside LogEvent

Listing 4, LogEvent.cpp, shows the implementation of function LogEvent. Step (1) converts LogEvent's va_list parameter to an array of pointers to strings based on the pszFormat parameter, as described above. This array is prepared as input for the Windows ReportEvent API (called under step (3)). As a matter of convenience, the Windows LocalAlloc call is used to allocate strings, since using the m specifier causes memory to be allocated using LocalAlloc indirectly via FormatMessage. Step (2) calculates the error type based on the two highest bits in the message id. The header file emitted by mc.exe documents these bits. Step (3) actually writes the event to the event log using the Windows NT APIs for writing to the the event log: RegisterEventSource, ReportEvent, and DeregisterEventSource.

Assumptions for Use

The LogEvent routine was written with the assumption that it would be called only occasionally, as Microsoft recommends for event logging. Furthermore, when creating a message control file, the first two bits of message identifiers should be kept to the default, as is recommended in Windows NT Event Logging, by James T. Murray (O'Reilly and Associates, Inc, 1998).

Avoiding Errors

Like printf, the LogEvent routine will crash or produce improper data if the parameters following the format parameter do not correspond to the format parameter. To avoid errors I find it helpful to specify strings first in message templates, followed by numerics. Furthermore, I try to account for the m format specifier possibly resulting in an empty string being substituted for a replaceable parameter, usually by placing such parameters last in message templates.

David Hooker was elected to Phi Beta Kappa in 1987 and received his BA in mathematics from the George Washington University in 1988. He has designed and written systems for mainframes and minicomputers in a variety of languages, with a concentration on Windows NT, Exchange Server, and MAPI in the last four years. He also has a strong interest in programming language implementation. He can be reached at DvdHkr@AOL.COM.