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.

Figure 1. The event logging mechanism

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 source parameters

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.

Figure 2. The event logging architecture

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 file
  • eventID: the event identifier defined in the message file
  • eventSeverity: the bitmask of the supported event types
  • eventCategory: 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'

Source: www.infoworld.com