Introduction
First, I would like to thank John Johnston and Mike Nordell for their help and suggestions. I also referenced the following books for information on event logging: Programming Server-Side Applications for Microsoft Windows 2000 by Jeffrey Richter, Jason D. Clark and Windows NT Event Logging by James D. Murray.
This is a simple tutorial for implementing event logging in a Windows 2000 application - it might also work with NT and XP, but I haven't tested the code with those operating systems. The source code file contains three projects that build on each other.
Message File Creation
The first project to be looked at is the MCGenerator project. This is a small dialog application I created to generate a stubbed message file that can be customized. An event can be associated with three different message files: an event message file, a category message file, and a parameter message file. All three files may be combined in to one message file. The stubbed message file created from MCGenerator contains a category file section and an event file section.
NOTE: I didn't implement a parameter file section.
To use the message file generator:
- Specify the file path and filename WITHOUT the .mc extension.
- Specify the number of categories.
- Specify the number of events.
- Click "Generate MC file".
- Click "OK" to terminate the file generated successfully pop-up window.
- Click "Cancel" to exit application.
The message file is a text file can be modified by any text editor. The message file now needs to be customized for the application.
;//**********************Category Definitions***********************
MessageId=1
Facility=Application
Severity=Success
SymbolicName=**SOME SYMBOLIC CATEGORY NAME WHICH IS USER DEFINED**
Language=English
**NAME OF THE CATEGORY WHICH IS USER DEFINED**
.
;//********************End of Category Definitions*******************
;// MessageIdTypeDef should NOT be altered
;// Event definitions are of type DWORD
MessageIdTypedef=DWORD
;//***********************Event Definitions*************************
MessageId=1001
Facility=Application
Severity=Success
SymbolicName=**SOME SYMBOLIC EVENT NAME WHICH IS USER DEFINED**
Language=English
**SOME MESSAGE WHICH IS USER DEFINED**
.
;//***********************End of Event Definitions***********************
Customizing the Category portion of the message file:
IMPORTANT NOTE: The "." terminates the category entry and must be followed by a carriage return.
The **SOME SYMBOLIC CATEGORY NAME WHICH IS USER DEFINED** needs to be replaced with a unique user defined name (eg. CATEGORY1). The **NAME OF THE CATEGORY WHICH IS USER DEFINED** needs to be replaced with some user defined string. This message is what is displayed in the event viewer's category field (e.g. Test Category 1 - see picture below).
These steps need to be repeated for each category defined in the message file.
Customizing the Event portion of the message file:
IMPORTANT NOTE: The "." terminates the category entry and must be followed by a carriage return.
The **SOME SYMBOLIC EVENT NAME WHICH IS USER DEFINED** needs to be replaced with a unique user defined name (eg. EVENT_1001). The **SOME MESSAGE WHICH IS USER DEFINED** needs to be replaced with some user defined message. This message is what is displayed in the event viewer's description field (see picture above).
These steps need to be repeated for each event defined in the message file.
The example category definition and event definition used to create the event in the screenshot above is defined below. %1 and %2 are place holders for arrays of characters that can be passed in from an application. %1 is the first passed in array and %2 is the second passed in array. It is also possible to pass in raw data associated with the event, however I didn't implement this.
;//**********************Category Definitions***********************
MessageId=1
Facility=Application
Severity=Success
SymbolicName=CATEGORY1
Language=English
Test Category 1
.
;//********************End of Category Definitions******************
;// MessageIdTypeDef should NOT be altered
;// Event definitions are of type DWORD
MessageIdTypedef=DWORD
;//***********************Event Definitions***************************
MessageId=1001
Facility=Application
Severity=Success
SymbolicName=EVENT_1001
Language=English
Username: %1, Password: %2.
.
;//***********************End of Event Definitions********************
Message DLL/EXE Creation
The second project file to be inspected is in the MessageDLL folder and contains the source code to make a message DLL from the message file just created. The event viewer links up to a message DLL or message EXE to retrieve the category and event information from your message file. A message DLL and a message EXE do the same thing and are interchangeable. I will outline the DLL procedure. This was a major stumbling block for me. Initially, I tried to integrate the message file into a Win32 app. Everything seemed to work with the Win32 app until I tried to view the event in event viewer. Upon viewing the event, I would get the following.
This error message went away when I move the message file to its own console app (EXE) or DLL project.
Creating a Message DLL:
In MS VC++, select File->New. Choose MFC AppWizard (dll) and define the Project Name and Location.
Click "OK". In the next pop-up dialog, accept the default options and click "finish" and click "OK" one more time.
The DLL project is now defined. MS VC++ now has to be setup to be able to compile a message file (I got this from John Johnston). This only needs to be done once to setup MC.EXE for MS VC++.
- In MS VC++, select Tools->Customize...
- In the Customize dialog select the "Tools" tab.
- In the "Menu Contents:" control click the New icon.
- Enter a tool name for the message compiler(I used Message Compiler).
- With Message Compiler highlighted, set the HD path to MC.exe in the "Command:" box - it is usually C:\Program Files\Microsoft Visual Studio\VC98\Bin\MC.EXE - browse to find it.
- Set the "Arguments:" box by clicking the arrow icon and selecting "File Name" from the list.
- Set the "Initial directory:" box by clicking the arrow icon and selecting "File Directory" from the list.
- Click the "Use Output Window" box to select this option. The dialog should look like what is in the picture below.
Now click "Close" to complete the setup.
The Tools menu heading should now have a Message Compiler entry under it.
Message file compilation and message DLL creation:
- The message file should be moved into the DLL project folder.
- Open the message file in the DLL project.
- Right click on the message file window and select "Insert File into Project."
- With the message file active, select Tools->Message Compiler. If done correctly the following should be in the output window.
MC: Compiling YourMessageFileName.mc Tool returned code: 0.
Compiling the message file will produce a header file (.h), a resource file (.rc), and a MSG00001.bin file. The resource file and the header file will have the same name as the message file.
NOTE: Make sure your message filename is different then your message DLL filename otherwise the DLL resource file will be overwritten with the message file resource file.
- 5. Click on the "ResourceView" tab.
- 6. Right click on the "DLL resource" heading and select "Resource Includes..."
- 7. Under
#include "afxres.h"
type in #include "xxxx.rc"
where xxxx is the name of the resource file generated by compiling the message file.
- 8. Click Ok and a warning message will appear (see picture below). This is normal, so click ok to continue.
Now select Build->Build xxxx.dll. The message DLL in now created.
Application Event Logging
The third project file to be explored is in the Logger folder and is a dialog application that logs events. The first step in implementing event logging is to set the registry information associated with the logging. I wrote a small wrapper class (LogRegisterMod) to handle the registry setup.
LogRegisterMod::CreateRegKey()
was designed to create the necessary registry entries using a CRegKey object(m_RegistryKey)
with is associated member functions and LogRegisterMod::RegSetString_EXPAND_SZ(HKEY hKey, LPCTSTR pszValueName, LPCTSTR pszString)
for REG_EXPAND_SZ
registry entry designation.
#define MY_LOGGER_REG_PATH
"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\Logger"
void LogRegisterMod::CreateRegKey()
{
this->m_RegistryKey.Create(HKEY_LOCAL_MACHINE, MY_LOGGER_REG_PATH);
this->m_RegistryKey.SetValue(524288,"MaxSize");
this->m_RegistryKey.SetValue(1,"CategoryCount");
this->RegSetString_EXPAND_SZ(this->m_RegistryKey.m_hKey,
"File","%SystemRoot%\\system32\\config\\Logger.evt");
this->RegSetString_EXPAND_SZ(this->m_RegistryKey.m_hKey,
"CategoryMessageFile","%SystemRoot%\\system32\\MessageDLL.DLL");
this->RegSetString_EXPAND_SZ(
this->m_RegistryKey.m_hKey,"EventMessageFile",
"%SystemRoot%\\system32\\MessageDLL.DLL");
this->m_RegistryKey.SetValue(EVENTLOG_WARNING_TYPE |
EVENTLOG_INFORMATION_TYPE | EVENTLOG_ERROR_TYPE,"TypesSupported");
this->m_RegistryKey.Close();
}
The MaxSize
parameter sets the maximum size of the .evt file and can also be set in the event viewer properties. The CategoryCount
parameter must match the number of categories defined in the message file. The File parameter allows the programmer to define the .evt file that stores the logs, otherwise the logs go to a defaulted file. The ParameterMessageFile
(not used), CategoryMessageFile
and EventMessageFile
sets the HD path to the message DLL. The TypesSupported
can be set to support all 5 event log types, this example only supports three types. See MSDN for more on event log types.
Note: REG_EXPAND_SZ
is required to allow expansion of "%SystemRoot%\\system32\\MessageDLL.DLL to c:\\winnt\\system32\\MessageDLL.DLL or g:\\winnt\\system32\\MessageDLL.DLL. REG_SZ
can only handle hard coded paths like c:\\winnt\\system32\\MessageDLL.DLL. The hard code path will cause problems if the logs are accessed remotely. In the remote access situation, the event viewer looks for the DLL and .evt file on the local machine instead of the remote machine where they reside.
Before proceeding, The message DLL needs to be copied to HD directory specified in the registry.
After the registry is set and the DLL is copied, the next issue to be resolved is linking the event source to the registry and the DLL. This is performed with the RegisterEventSource
function. I wrote the wrapper class EventLogging
to take care of the source registering, event logging, and source deregistering. The lpSourceName
argument in RegisterEventSource
must be that same as the subkey defined in the registry ("Logger" in this example).
From the LogRegisterMod
class:
#define MY_LOGGER_REG_PATH
"SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\Logger"
From the EventLogging
class:
EventLogging::EventLogging()
{
this->m_hEventLinker = RegisterEventSource(NULL,"Logger");
}
With the EventLogging
class, the code required to log an event is shown below.
LPCTSTR lpStrings[] = {m_cszUSERNAME,m_cszPASSWORD};
UINT NumOfStrings = 2;
this->m_pEventEntity->LogIt(1,1001,lpStrings,NumOfStrings);
Actually, m_pEventEntity->LogIt(1,1001);
is all that is required to log an event--but doing so restricts the event logging to static messages defined exclusively by the message file. The "1" is the Category Id# and the "1001" is the Event ID#. The SymbolicNames
may be substituted for the the ID#'s as long as the message file header (generated from the message file compilation) is included in logging applications source file. The example above allows messages to be defined by the message file in combination with character arrays defined at run time.
The LogIt
function wraps the ReportEvent
with all arguments defaulted except for CategoryID
and EventID
. This is what allows an event to be logged with as little as a Category ID and Event ID or as much as Category ID, Event ID, multiple arrays or characters, and raw data.
virtual void LogIt(WORD CategoryID, DWORD EventID,
LPCTSTR ArrayOfStrings[] = NULL,
UINT NumOfArrayStr = 0,LPVOID RawData = NULL,DWORD RawDataSize = 0);
Using the Demo Project
Extract the message DLL and the executable from the zip file. Move the message DLL to c:\WINNT\system32\. Run the Logger executable. Enter anything into the Username and Password edit boxes and click "Logit". Click "Ok" in the pop-up message box and click "Cancel" to terminate the Logger application.
Go to Start->Settings->Control Panel. Double click the Administrative Tools icon under Control Panel. Double click the Event Viewer Icon. Click the Application Log and double click event that has Logger as the source. The information entered into the Logger Application should be in the Event Description.