The use of NT event logging is very familiar to those working on NT-based (Win2000, XP) client/server systems. Since this article is classified as beginner, here is the idea behind NT event logging: prior to NT. If an application coder wanted to record errors and events, it would be necessary to create some proprietary error log format as well as logging mechanism (API). These proprietary error logs would also need proprietary UI viewer app as well. Things rapidly get worse when different in-house and third-party apps all write their own error logs - you have to check each log (that is, if you know where to find it).
OK, now comes NT. (Note - some of this info taken directly from MSDN). NT event logging provides a standard, centralized way for applications (and operating system) to record important software and hardware events. The event-logging service stores events from various sources in a single collection called an event log. There are three pre-defined logs that you can view with NT Event Viewer: Application, Security, and System logs. The NT Event Viewer enables you to view and manage logs; and its API enables you to write and examine logs. The Application log contains events reported by user-mode applications, and is the log that your application will write to. By the way, if it isn't clear yet, let me state that NT event logs are not available on Win9x.
As an example, open Event Viewer now: go to Settings | Control Panel | Administrative Tools | Event Viewer. Click on Application in left pane to view its events. The last event logged will be displayed at the top of the list.
Right-click on Application in left pane, then choose Properties. You will see Application Properties dialog, which allows you to specify location, maximum log size, and filters. Note also Clear Log button on dialog, which you may want to use to clear Application log before starting a debug session. (You can clear the Application log now if you want.)
A few other things to note about NT Event Viewer user interface: when you left-click Application in left pane, the menus and also toolbar buttons change. For example, you will now see Refresh toolbar button, which is necessary to use if you want to view recent log additions (most developers just leave NT Event Viewer open, and hit Refresh button to view new entries). The NT Event Viewer has many other features, such as exporting log file to an external file. See Event Viewer help, or MSDN topics, for more details.
Now for meat: When you double-click an event in right pane, NT Event Viewer opens Event Properties dialog that shows details about event as well as full message text. Now, leave Event Viewer open, and start XEventLogTest demo app. You will see this:
Just use default settings, and click Write Log. In Event Viewer, click Refresh button, and new events will appear in list (note: Error event illustrates how to do multi-line strings in event log, see below for details):
Double-click Information event, and you will see:
Wait a minute! Where does all this stuff in Description box come from? Well, you've just seen the number 1 problem in writing to the NT event log: before you can write events to log, you must first create a message file (typically, message file is resource-only DLL). Here is what this means: In the Microsoft view of things, you are expected to store strings for your app in a message DLL. This message DLL encapsulates the output of Microsoft's Message Compiler (mc). In order to log a message, the app must specify the file path of this message DLL - Microsoft calls this "registering an event source". You register this event source in the usual Microsoft manner - by writing directly to the registry (HKLM). You can use Regedit to see the registry keys that XEventLog creates:
You can then call an event logger API (RegisterEventSource()) to get an event source handle, and finally you use this handle to write to event log with another event logger API (ReportEvent()).
Whew! OK, we have logged an event. Now, this is the part about event logging that is really non-obvious (at least it was to me). The ReportEvent() API has no interaction whatsoever with the message DLL. It is only when you use NT Event Viewer that the message DLL will be loaded, and its compiled messages used. If the Event Viewer can't find message DLL, you get garbage that you see in the above screenshot. If you put message DLL in proper location, Event Viewer will no longer show garbage.
Now let me jump ahead to show you what happens when message DLL is properly registered, and Event Viewer can find it at the location that you specified in registry:
Now you can easily see the message you logged. And here is the incredible thing: it is very easy to do this, with just one DLL - XEventMessage.dll - that you can reuse unchanged in all your projects. Plus, you don't even have to link to this DLL - it just has to be in your exe's directory, or in some common shared directory. Add two files to your project, and you're ready to log some events. Forget about all that Message Compiler stuff you've read about. No screwy syntax to figure out, no need to transfer all your message strings to some .mc file. It's really this easy! - I even give you XEventMessage.dll to drop into your exe directory (I give you source for it too, if you really like to compile stuff).
But what about parameters, like
printf() uses? Doesn't Message Compiler support multiple strings? Yes, it does, but you have to decide whether you want to keep going back and forth between your app and tweaking the message DLL. What if you want three strings? four? five? It is much simpler to do the message formatting in the app (say, with CString's
Format() method, that you already know) than to update separate DLL.
Here is example code to log formatted multiple-line values:
DWORD dwThreadId = 0xDEADBEEF;
dwThreadId, "Can't delete file", "File does not exist");
and here is what multi-line log event looks like:
So, while Microsoft documentation would lead you to think that it is necessary for each app to have its own message DLL (and I haven't even mentioned category DLLs or parameter DLLs!), in real world, very few apps do this. It is difficult enough just keeping app up-to-date, let alone DLL too. Today, most apps just pull strings from string resources. To sum up, the Message Compiler may be necessary in some situations, but my goal here is to provide a minimalist drop-in message DLL that is suitable for any app running in production environment. In effect, the message DLL that I present here is simply a pass-thru DLL that is independent of any particular app, and so can be used by many different apps without change.
How To Use
CXEventLog class into your app, you first need to add following files to your project:
Also remember to copy XEventMessage.dll
to the exe's directory. If you want to test both Debug and Release versions of app, XEventMessage.dll
must be in both directories.
If you include XEventLog in project that uses precompiled headers, you must change C/C++ Precompiled Headers settings to Not using precompiled headers for XEventLog.cpp.
Next, include the header file XEventLog.h in appropriate project files (stdafx.h usually works well). There are two ways to construct
CXEventLog object. Typical way is to construct object at runtime - for example, in app's
m_pEventLog = new CXEventLog(str);
If object is allocated from heap like above, don't forget to delete it!
The second way to construct
CXEventLog object is to create local object:
el.Write(EVENTLOG_ERROR_TYPE, _T("This is a sample error."));
or you can also write this as:
el.Write(EVENTLOG_ERROR_TYPE, _T("This is a sample error."));
You must call
if you do not specify app name in
object created, you can now call
to log events. Note that the event log can be used for much more than just logging errors - see this FAQ
for more information.
Frequently Asked Questions
- Does XEventMessage.dll have to be put in WINDOWS or other system directory?
No! It is best not to put in system directory, and Microsoft discourages this too. Put XEventMessage.dll in same directory as your exe, or in some common directory (say, under Program Files\My Company\Common).
- I want to use XEventMessage.dll from several apps. Do I have to put it into each app's directory?
No. The ctor second parameter is fully-qualified name of event message dll. This includes complete path and file name.
m_pEventLog = new CXEventLog("myapp", <BR> "C:\\Program Files\\My Company\\Common\\XEventMessage.dll");The ctor second parameter is optional, and will default to the exe's directory if not specified.
- The demo app runs fine on Win98. Why do you say it won't work?
The demo app may run without errors, but that's because there are stub APIs on Win9x that do nothing at all. There is no event log service on Win9x, so nothing gets logged! The good news is that your app will work on both NT and Win9x machines; the bad news is nothing is logged on Win9x.
- Can I use XEventLog in non-MFC apps?
Yes. It has been implemented to compile with any Win32 program.
- Can I use XEventLog in a DLL?
- When I try to include XEventLog.cpp in my MFC project, I get the compiler error
"xeventlog.cpp(439) : fatal error C1010: unexpected end of file while looking for precompiled header directive". How can I fix this?
When using XEventLog in project that uses precompiled headers, you must change C/C++ Precompiled Headers settings to Not using precompiled headers for XEventLog.cpp.
- The XEventMessage project in the download only has one configuration - Release. Don't I need XEventMessage Debug / Unicode configurations also, if my app has those configurations?
No. The Release-mode XEventMessage.dll is the only one included because it's the only one that's needed, whether your app is compiled for Release, Debug, ANSI, or Unicode. Note that there is no code in DLL, so nothing needs to change.
- Do I have to recompile XEventMessage.dll before using it in my app?
Not unless you want to test your compiler. It can be used "as is".
- Do I need to make registry changes before using XEventLog?
No. XEventLog code creates the necessary registry keys for you, when you create
CXEventLog object. To see what these registry keys are, please look at code.
- Does XEventLog delete the registry entries that it creates?
No. In a production environment, it is assumed that app will be run more than once, so there is usually no requirement to delete the registry entries. Personally, I get really nervous when a program goes through my registry, deleting keys. The chances of messing up the whole system because of some simple programming mistake, is something I don't want to think about, especially when dealing with HKLM. I trust myself with Regedit more than I trust code someone has just handed me.
- Isn't it possible to include the compiled message resource in exe?
Yes. It is possible, and this could be done if your situation requires it. For example, if you are only dealing with single exe, and there are reasons to distribute your app with as few files as possible, then this solution would make sense. My goal here is to present production-oriented solution - that is, environment where there are many apps, so that including message resource in each app just snowballs out of control.
In production environments, my experience is that you must do whatever you can to modularize components, establish common (shared) code repositories, and do things only once instead of many times. In this way, you increase system robustness, and using same code over and over gives you confidence in common components. (This last is not appreciated until you have difficult system problem, and you must decide what to suspect, what to trust.)
- Why doesn't XEventLog support categories?
If you look at any of the logs with NT Event Viewer, you will see that nearly all events have Category = None. I suspect this is because no one really knows what Category is for, or how it would help them. Having said that, the main reason for not supporting categories in XEventLog is that it would add extra complexity, with very little gain (IMHO).
- I included XEventLog in my app, but I get a lot of garbage in the Description box. What is wrong?
You must copy XEventMessage.dll to exe directory. This means it must be in both Debug and Release directories. Note that even if XEventMessage.dll is missing, your message will still be logged, but it will have garbage in front of it in Description box. After you copy XEventMessage.dll to exe directory, refresh Event Viewer, and garbage will be gone.
- Can we use XEventLog and redistribute XEventMessage.dll in our (shareware/commercial) app?
Yes, you can use XEventLog without charge or license fee. It would be nice to acknowledge my Copyright in your About box or splash screen, but this is up to you.
- Should I log only errors and other alerts to the event log, or can I use it for other kinds of messages?
You can log any kind of information you want. For example, several commercial apps that I am familiar with use event log to log debug trace information. Since you lose all TRACE() output when you compile for Release, it is handy to be able to turn on some trace output for Release builds, especially if you have to go to customer's site. You can do this by creating registry (DWORD) flag, and then write to event log if flag is turned on (Use Regedit to create flag). The default, of course, is not to write. If you plan to do this, be sure event log size is set to appropriate value before you begin, and also be sure to disable flag when you are finished.
Here is some pseudocode that will filter log output, based on flag:
void DebugLog(LPCTSTR lpszString)
Get flag from registry.
Is flag set?
If so, call CXEventLog::Write()
Instead of calling
CXEventLog::Write() directly for your debug trace output, you call
DebugLog(), and it only writes to event log if flag is set. This centralizes debug output, so you don't have to search through code if you want to disable this feature, etc. A convenient place to put
DebugLog() is in your
CWinApp-derived class (another reason to declare
CXEventLog object there).
An interesting feature to add to
DebugLog() is to use the Win32 function OutputDebugString() to optionally write to debugger. (Yes, this works fine in Release build). This is handy if used in conjunction with excellent DebugView from Sysinternals. So the above code could be written as:
void DebugLog(LPCTSTR lpszString)
Get flag from registry.
Is flag set?
If so, call CXEventLog::Write().
Get OutputDebugString flag from registry.
Is flag set?
If so, call OutputDebugString().
Using DebugView, the string will be displayed if OutputDebugString flag is set (Note: OutputDebugString flag is a DWORD key that you create in registry.)
By using combination of writing debug trace to event log, and optionally to debugger, it will be much easier to diagnose Release-build problems.
- Why did you write CXEventLog?
I got tired of looking at garbage in Description box, and even more tired of explaining to people why garbage was there. It looked like I didn't know what I was doing.
- Don't I need to include the message DLL's header file in my project? What is the connection between the CXEventLog class and the XEventMessage.dll?
No, there is no need to include Message.h. There is only one piece of information that is needed by
CXEventLog (besides the filename of XEventMessage.dll), and that is the fourth parameter to ReportEvent() - the event identifier. This is defined in the XEventMessage project file Message.h as
#define GENERIC_MESSAGE 0x20000001LIf you look at XEventLog.cpp, you will see that the fourth parameter to ReportEvent() is actually
0x20000001L. This eliminates the need to include Message.h, since there is no possibility that
GENERIC_MESSAGE will change (there is only one message defined in the .mc file).
- So what is in XEventMessage.dll's .mc file?
OK, here it is:
Version 1.0 - 4.16.03
This software is released into the public domain. You are free to use it in any way you like. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.