Sunday, May 8, 2005

Creating an event message file for the event log with Delphi

When creating a service application you usually log important events to the Windows event log by using the Windows API function ReportEvent().

This works without any problems with Delphi, however since Windows expects a so called message file to display the logged events using eventvwr.exe, the user will see all your events starting with "The description of the event id xxx was not found in…". This is because you did not provide an event message file and thus Windows displays this stupid message. Proving an event message file is very simple and I'll show you how.

First of all, you need to understand why Windows relies on event message files. When Windows NT was created (the first Windows with an event log) one goal of the development team was globalization. This means that Windows NT could be easily transferred into any language. Since an event usually contains one part that is always the same (e.g. "The following file was not found: ") and a variable part, like the filename that was not found, the team had the idea of event message files.

Inside the event message file, the common part will be noted like "The following file was not found:". The data (like the file name) will be submitted by the application logging the event and joined with the event message file. This way, the final message would look like "The following file was not found: C:\test\arg.txt". Using this technique, you could easily give the event message file a translator and let him translate the text inside this file. For example, the German translation might read "Die folgende Datei wurde nicht gefunden: ". When you then install this translated message file, all messages of the program are translated without a single change inside the application itself. This is because the server application does not contain the language-specific strings, it does only write an ID (e.g. 100) and the variable data. The text for ID 100 will then be retrieved from the translated event message file.

So much about the theory, let's get this rolling. To create an event message file you first need to fire up Delphi and select File - New - DLL. Remove the "uses" statement completely since we won't need this.

If you want your DLL to later on have version information, you should directly enable this by using Project - Version Info - Include version info in project.

Save this project and compile it once. You should now have a DLL with round about 18 KB in size. Go to the folder where you saved this project and copy the *.RES file to a new file, e.g. eventlogmessages.res.

You'll now need the PE Resource Explorer from http://www.wilsonc.demon.co.uk/d7resourceexplorer.htm. Download, install it and run it. Select File - Open -> Type = Resource Files (*.RES) files and open the newly crated eventlogmessages.res.

Add a message table resource block by using Resource - Add Resource - Message Table. Since Delphi has properly created an Icon Group resource, open this branch and delete the icon at the end of this branch by using Resource - Delete Resource, we do not need any icon in this file.

Now, open the Message Table resource, open the "1" key and finally open the "Language Neutral" branch. On the right side of the Window you will now see a list view of all messages defined in this file. Of course the list is empty so right click the list and select "Add String". PE Resource Explorer will automatically add a string with the ID of 1 and we can enter some text. Because we first want to test this feature, we'll enter "My cool event. Data: [%1] Even more text". This does of course not make any sense; it is simply a demonstration how the event message file works.

Save the filer and exit PE Resource Explorer. Back in Delphi, change the line " {$R *.RES}" to be " {$R eventlogmessages.RES}" and finally recompile it. This will merge the event message with have just created into the DLL so we have now an event message file!

However, at this time the event log viewer (eventvwr.exe) does not know that this event message file exists, so we need to a little bit more to register our file with Windows.

The event log viewer expects to find all event message files in the registry path HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\[Application Name]. For example, if your application uses ReportEvent (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/reporting_an_event.asp) with the event source name of "MyCoolApp" the registry path has to be HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\MyCoolApp. Below this path there are only two values that need to be set.

The first is of type REG_SZ or REG_EXPAND_SZ (if you want to work with %VARIABLE%) with the name of "EventMessageFile". This value should point to the event message file, for example c:\Projects\EventLogDll\eventlogdll.dll.

Secondly, it should contain a REG_DWORD value with name "TypesSupported" that will simply tell Windows will types of events your application logs. This is a bit mask of the following values:

1 = Error
2 = Warning

4 = Information

8 = Audit Success

16 = Audit Failure

So, for example your application only logs Warnings and Errors, you would enter 3 (decimal) into this field (Warnings (2) + Error (1) = 3). If you do not want to use this bit mask, simply enter decimal 31 (HEX: 1f) which will tell Windows that your application can log any event type.

Well, this is it. When you now use ReportEvent() with the event source name of "MyCoolApp" and the ID of 1 and a event data of "x234" the Event Viewer will display:

My cool event. Data: [x234] Even more text.

For a very simple application, you might decide to only have one entry in the message table with the ID of 1: "%1". This will add no additional text to your message but instead simply write whatever the application logs. Of course, this means all your events will have the ID of "1" regardless of what the text says.

Most administrators expect a service to log different IDs for different events so they are easily identified. For example, you might decide that event 100 should by "Application started", event 200 "Application ended", 101 stands for "Fatal Exit" and so on.

How many events you log is up to you but you should always keep in mind that each event ID you log using the service, you'll need to provide in the event message file as well.

In this case please remember that the IDs inside a message table are noted in HEX, while the event viewer will display them decimal. For example, if you want an event with the ID 100, you need to write event 100 with ReportEvent() and the event viewer will also display this event as 100. However, inside the event message file, the ID for this event would be "64" (Dec 100 = Hex 64). If you would write the ID 100 into the event message file, this would be the text for the event 256 (Dec 256 = Hex 100). Please keep this in mind.

6 comments:

  1. Thank you Michael, the description you give of how to create an event message file is brilliant. It's one of the clearest descriptions of how to do something I have ever found! Not just "how to" but also why.

    Many thanks

    ReplyDelete
  2. very good post ... thank you very much !!! now I am trying to find out more information about the TService.LogMessage ... I am having problems identifying the Category.

    ReplyDelete
  3. Thanks for this - I found it really helpful.

    ReplyDelete
  4. Don't bother with a separate dll. Make a .res file with just a message table.
    00: %1
    01: General
    02: Debug info
    Embed it in your .exe by entering
    {$R Messages.RES}
    just below
    {$R *.RES}
    in the project source file, and then in AfterInstall:
    If myReg.OpenKey('\SYSTEM\CurrentControlSet\Services\Eventlog\Application\'+self.name,True) Then
    Begin
    myReg.WriteString('EventMessageFile',ParamStr(0));
    myReg.WriteInteger('TypesSupported',7);
    myReg.WriteString('CategoryMessageFile',ParamStr(0));
    myReg.WriteInteger('CategoryCount',2);
    myReg.Closekey;
    End;

    Remember to clean up in AfterUninstall.

    ReplyDelete
  5. This is an old post, but I wanted to respond to Gamlefar's suggestion.

    We just moved from having the messages linked in the EXE to using a separate DLL. The reason is that the Event Log service will open a handle to your EXE and keep it open even when your application/service is not running.

    This was causing us a lot of grief when trying to deploy an updated version of our application.

    ReplyDelete
  6. Tried to download your PE /XN) editor, but links won't work anymore :-(

    Moved to dev 0 ??

    ReplyDelete