Log it or lose it
Log events to the Windows NT Event Log with JNI
Almost all Java middle-tier components need to log such events as database SQL statements, Java exceptions, and function entry and exit points. Those Java applications running on Windows NT must log events into the NT Event Log, the central repository for all such events.
However, the JDK does not directly support writing to the NT Event Log. To do so, you need to expose a method in the JNI (Java Native Interface) DLL (Dynamic Link Library) to direct the events to the NT event viewer.
This article has three sections. The first covers logfiles, event sources, event categories, event identifiers, and event messages. The second section demonstrates how to create a message file as a DLL, how to make the DLL self-registering, and how to create a JNI DLL that exposes a method to direct events to the NT Event Log. Finally, you’ll see a sample Java program illustrating the JNI method in action.
Note: The discussion in this article applies to Windows NT version 4.0 and higher.
The event logging mechanism
The NT Event Log, a Windows NT service that starts whenever Windows NT boots, logs warnings, exceptional conditions, and other administrative messages, all of which it writes to event logfiles. Since the service uses RPC (Remote Procedure Call), you can view and log the messages from remote machines.
You can employ event logging to:
- Catch exceptional conditions and log them for support staff.
- Read invalid values.
- Synchronize the sequence of applications by using the NT Event Log as a central facility. By logging the source name and time, the applications can check the events and start them in the correct order.
Applications report events by calling the ReportEvent()
function. The system passes the parameters to the event logging service, which uses the information to write the event log record to the event logfile. Figure 1 illustrates the process.
The major elements used in event logging include:
- Logfiles
- Event sources
- Event categories
- Event identifiers
- Event messages
Let’s examine each in turn.
Logfiles
The event logging service uses information from the EventLog
registry key when an application writes to and reads from the Event Log. The EventLog
key (shown in the following example) contains several subkeys, called logfiles. The logfiles allow the event logging service to locate the resource for a particular application to enable it to perform logging services. The default logfiles are Application
, Security
, and System
. The structure in the registry is as follows:
HKEY_LOCAL_MACHINE
SYSTEM
CurrentControlSet
Services
EventLog
Application
Security
System
Applications and services use the Application
logfile, while device drivers use the System
logfile. When you turn auditing on, the system generates success and failure audit events in the Security
log.
Event sources
Each logfile contains subkeys called event sources — the name of the software that logs the event. The structure is as follows:
HKEY_LOCAL_MACHINE
SYSTEM
CurrentControlSet
Services
EventLog
Application
AppName
Security
System
DriverName
Each event source contains information specific to the software that will log the events, such as the message files, as shown in the table below.
CategoryCount |
Specifies the number of event categories |
CategoryMessageFile |
Specifies the path for the event message file that contains locale-specific strings describing the categories |
EventMessgeFile |
Specifies the path for the event message file that contains locale-specific strings describing the events |
TypesSupported |
Specifies the bitmask of supported types, which can be one or more of the following: EVENTLOG_ERROR_TYPE , EVENTLOG_WARNING_TYPE , EVENTLOG_INFORMATION_TYPE , EVENTLOG_AUDIT_SUCCESS , and EVENTLOG_AUDIT_FAILURE |
Event categories
Event categories help you organize events so event viewers can filter them. Each event source defines its own numbered categories and the text strings to which they are mapped. For example, in a data-intensive application, events could be: data write error, data read error, SQL error, and so on. Events would then fall under these categories.
Event identifiers
Event identifiers uniquely identify a particular event. Each event source can define its own numbered events and the description strings to which they are mapped. Event viewers can present these strings to the user.
Event messages
The event messages reside in message resource files containing parameterized strings. When the event message is written to the Event Log, it does not store the entire message string, but rather just the inserted values passed as parameters. The Event Log uses the event identifier to map to the event message displayed in the event viewer. There are two benefits to this arrangement:
- Most of the descriptive text can be stored in the resource file, thus decreasing the size of the logfiles
- The message strings can be made specific to the locale, enabling you to provide a separate resource file for each locale, thus making the application locale independent
Each event source registers message files that contain description strings for each event category and event identifier. These files become registered in the EventMessageFile
and CategoryMessageFile
registry values for the event source. You can create one message file containing descriptions for the event identifiers and categories, or create two separate message files. Keep in mind that several applications can share the same message file. You should typically create message files as DLLs.
Use the Event Log
So, how do you read and view the NT Event Log?
An event viewer application employs the OpenEventLog
function to open the Event Log for reading events. The event viewer can then use the ReadEventLog
function to read event records from the log. Figure 2 illustrates the process.
When the user starts the event viewer to view the Event Log entries, the event viewer calls the ReadEventLog()
function to obtain the Event Log records. The event viewer uses the event source and event identifier to retrieve the message text for each event from the registered message file (indicated by the EventMessageFile
registry value for the source). The EventMessageFile
registry key contains the path of the message DLL. The event viewer uses the LoadLibraryEx()
function to load the message file. The event viewer then employs the FormatMessage()
function to retrieve the description string from the loaded module.
Writing to the NT Event Log in Java
Now that we’ve covered the NT event logging basics, let’s turn our attention to a Java application that writes event messages to the NT Event Log. First, you’ll see a DLL that contains the message definitions in the message file. Next, you’ll see a JNI DLL that logs events. A sample Java program logs the events into the Event Log by calling a function of the JNI DLL. The JNI DLL logs the events into the Event Log by referring to an event source that has already been registered. The details are described in the following sections:
- Create message files
- Create the JNI DLL
- A sample program that writes to Event Log
Create message files
Message files are either .exe
files, or DLLs with an associated message-string resource type. You build such resources with the MC.EXE
message-compiler utility included in Microsoft Visual C++. A Win32 DLL project that contains the message file can be created in Visual C++. For our example, we created SampleAppMessages.dll
in Visual C++ 6.0 for both messages and categories. It is a self-registering/deregistering DLL that can register and deregister the SampleApp event source into the Event Log using the regsvr32
utility.
Message definitions
SampleAppMessages.dll
contains the SampleAppMessages.mc
message file with the message and category format strings the sample Java program employs to log events in the Event Log. SampleAppMessages.mc
includes the mc SampleAppMessages.mc
custom-build command that produces the SampleAppMessages.rc
resource file. Moreover, we’ve incorporated the SampleAppMessages.rc
file into the Visual C++ project SampleAppMessages.dsp
.
The message file SampleAppMessages.mc
comprises two sections:
- Header
- Message format strings
You must define MessageIdTypedef=DWORD
in the header section. The header section’s other items, such as Language, Severity, and Facility, receive the default values of International English, Success, and Application, respectively. Therefore, you don’t specify them in the header section.
The message-format string contains the definition of both category- and message-format strings to create a single DLL for both the event and category messages. The category-format strings resemble message-format strings, but they begin with a MessageID
of 0x1
and do not possess any insertion strings. We have defined two categories for the SampleApp:
- Data read
- Data write
You’ll find them defined in SampleAppMessages.mc
as:
;define CAT_DATA_READ 1
MessageId=0x1
Language=English
Data Read
.
;define CAT_DATA_WRITE 2
MessageId=0x2
Language=English
Data Write
.
Notice that the symbolic names — CAT_DATA_READ
and CAT_DATA_WRITE
— double as comments. The MC compiler ignores these, but inserts them in the header file as #define
for use in the application. For example, the compiler inserts define CAT_DATA_READ 1
in the header file as #define CAT_DATA_READ 1
.
We further defined two messages as:
MessageId=0x1000
SymbolicName=SQLSTMT
Language=English
SQL Statement=%1
.
MessageId=0x1001
SymbolicName=ERROR
Language=English
The Error is=%1
.
The SQLSTMT
and ERROR
format strings contain one placeholder apiece; it is indicated by %1
, which gets replaced by the insertion strings supplied to ReportEvent
by the SampleApp for logging SQL statements and errors.
Register and deregister SampleAppMessages.dll
With NT’s regsvr32
utility, SampleAppMessages.dll
can register and deregister the SampleApp as the event source with the Event Log. The regsvr32
utility is invoked as regsvr32 SampleAppMessages.dll
for registering the SampleApp with the Event Log. regsvr32 -u SampleAppMessages.dll
is executed to deregister the SampleApp from the Event Log. The SampleAppMessages.dll
exports two functions, DllRegisterServer()
and DllUnregisterServer()
, that register and deregister the DLL with the Event Log using Window APIs such as RegCreateKey
and RegSetValueEx
.
The DllRegisterServer()
creates a registry key SYSTEMCurrentControlSetServicesEventLogApplicationSampleApp
. It sets the value for EventMessageFile
and CategoryMessageFile
as the full directory path for SampleAppMessages.dll
. It sets the value of CategoryCount
to “2” to reflect the number of categories defined in SampleAppMessages.mc
. It also sets TypesSupported
as EVENTLOG_ERROR_TYPE
, EVENTLOG_WARNING_TYPE
, or EVENTLOG_INFORMATION_TYPE
out of the event types supported by the Event Log. DllUnregisterServer()
deletes the SYSTEMCurrentControlSetServicesEventLogApplicationSampleApp
to deregister the SampleApp from the Event Log.
Create the JNI DLL
JNIEventLog.dll
, a Windows DLL created in Visual C++, acts as a JNI DLL, which the SampleApp Java program loads for logging events. The JNIEventLog.dll
implements a JNI method, described below, that logs events into the Event Log.
The native method in SampleApp.java
is:
private native void logMessage(String message,
int eventID,
int eventSeverity,
int eventCategory);
The function logMessage()
possesses these parameters:
message
: replaces the placeholder in the message string described for the event in the message fileeventID
: the event identifier defined in the message fileeventSeverity
: the bitmask of the supported event typeseventCategory
: the category ID to which the event belongs; also defined in the message file
The C header file generator: javah
After compiling the SampleApp Java application, run the javah
utility on SampleApp.class
as:
javah -jni SampleApp.class
javah
reads the classfile, and, for each native method declaration, it generates the function prototype in a C or C++ header file. javah
‘s output is a SampleApp.h
file with the following declarations:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class SampleApp */
#ifndef _Included_SampleApp
#define _Included_SampleApp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: SampleApp
* Method: logMessage
* Signature: (Ljava/lang/String;III)V
*/
JNIEXPORT void JNICALL Java_SampleApp_logMessage
(JNIEnv *, jobject, jstring, jint, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
Here, the JNIEnv
parameter acts as a hook allowing callback into the JVM. The jobject
parameter is the reference to the object that called the native method.
Implement JNIEventLog.dll
The Visual C++ project JNIEventLog
, which includes the SampleApp.h
generated by javah
, implements the Java_SampleApp_logMessage()
JNI method:
JNIEXPORT void JNICALL Java_SampleApp_logMessage
JNIEnv *env,
jobject obj,
jstring message,
jint eventID,
jint eventSeverity,
jint eventCategory)
{
const char *msg = env->GetStringUTFChars(message,0);
WORD wEventID = (DWORD)eventID;
WORD wEventSeverity = (WORD)eventSeverity;
WORD wEventCategory = (WORD)eventCategory;
//Get the handle to the event source 'SampleApp'
HANDLE h = RegisterEventSource(NULL, L"SampleApp");
if(h != NULL){
//convert jstring inot WCHAR
WCHAR *szMsg[1];
zMsg[0] = (WCHAR *)malloc( 2 * strlen(msg) + 2);
for(unsigned int i = 0; i < strlen(msg); i++) {
szMsg[0][i] = (WCHAR)msg[i];
}
szMsg[0][strlen(msg)] = L'