Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

A Portable Distributed Event-Logging Facility


Sep01: A Portable Distributed Event-Logging Facility

Ivan is a founding partner of APP Design Group Inc. He can be contacted at [email protected].


Every enterprise scale application needs an event-logging facility that can be used in design, development, and deployment. Why? Well, for one thing, each application component — a user-interface program, batch job, mid-tier service, or whatever — generates various log messages (or, more generally, run-time events). These events need to be recorded to monitor the state of the application or for postmortem analysis. However, application events are not generated equal and have to be dispatched according to their types.

There are several extreme solutions to the problem. For instance, at a low level, there are OS or network facilities such as UNIX's syslog, NT's event log, and SNMP-based services that are not readily available for applications and, usually, are considered to be a domain of hard-core system administrators. At a higher level, however, there are numerous language-specific logging toolkits. For instance, the most widely known for Java are JLog (http://www.alphaworks.ibm.com/) and log4j (http://jakarta.apache.org/) that are not designed for use with other languages, which is especially limiting in a distributed enterprise environment. In the midrange, the OMG Notification Service Specification (see "Notification Service Specification, Version 1.0," http://www.omg.org/) possesses all the traits of the architecture designed by a committee — being very complete, it is not that suitable for anything budget conscious and/or time constrained. (Does it sound familiar?)

In this article, I present a distributed portable event-logging facility that is simple, flexible, relatively platform independent, and available to applications written in several languages (including scripts). Listings One through Eight, which implement the logger, were extracted from my company's APPAnvil toolkit (http://www.appdesigngroup.com/) and were significantly simplified for presentation purposes. I've tested the examples on Windows NT 4.0 SP6 using JDK 1.3, VisiBroker for Java Version 4.0, and Perl Version 5.005_03 from ActiveState, Build 522. The complete source code and related files (including make and IDL files) are available electronically; see "Resource Center," page 5.

This general-purpose event-logging facility has three major parts:

  • Event production.
  • Event transportation to the log service.
  • Event dispatch (storage, alerts, displaying, and so on).

As is often the case in the distributed world, the appropriate place to start a design is not the beginning (event production) nor the end (event dispatch), but the middle (transportation).

I settled on CORBA as the transport method because CORBA implementations are available on many hardware and operating-system platforms, it can be directly accessed from Java and C++, and CORBA implementations usually take care of low-level networking (connection maintenance, redundancy, fail over capabilities, and so on).

The simplified event-logging interface in Example 1 contains a minimally usable amount of information about a remote event, namely:

  • A name of the machine that generated the event.
  • An event type.
  • Event source, usually a place in a program from which an event originated (for example, an application name combined with a class name and a method name).
  • A human-readable text message.

Having an event type as a string instead of an enumeration provides flexibility for managing the event set without changing the interface. For example, event types can be added or removed and nothing has to be recompiled. (For the sake of brevity, I omit many implementation details throughout the article. These details are mostly about proper initialization, error handling, configuration, usability, and resilience.)

Figure 1 presents the general architecture of this facility. The core of it is the EventLogService that implements the EventLog interface and provides actual event logging.

Client Side

Accessing the interface in Example 1 is straightforward from any language that has direct CORBA mapping (Java or C++, for instance). Consider a Java implementation. First of all, it would be extremely inconvenient if an application needed to make a CORBA connection every time an event needed to be logged. It would not only tax performance, but lead to code bloat. Second, it pays to integrate event logging into the exception class hierarchy; then everything thrown is logged automatically. These considerations lead to the implementation in Listing One, which is a wrapper class that maintains a static connection to the Event-Log service. Listing Two is an example of how you might use the interface. Events can be logged either by calling a static Log.log() method or by throwing a Log exception (or its heirs). The Log() and Log(String) constructors provide the full compatibility with the Exception class when no logging is required by the derived classes.

Event logging becomes more interesting for languages that do not have direct CORBA interfaces. There are two possibilities:

  • Build a bridge between a programming environment, which is not CORBA compliant, and a C++ or Java program using whatever appropriate method for each combination.
  • Come up with some general bridge that will handle most of the cases and provide a somewhat less elegant solution for the environments not supported by this bridge.

Since the second approach would seem more economical, I'll consider it first. This general-purpose bridge translates some proprietary event protocol into CORBA calls. The simplest approach is to select a protocol that can be implemented by most programming languages and implement a bridge just for it. On the network level, the UDP looks particularly suitable — it is easy, fast, and widely available for most modern programming environments. On the application level, delimited ASCII string suffices for our purposes. Listing Three is the implementation of the UDP bridge, which starts as a service that listens for a well-known UDP port. As soon as an event message is received, it is parsed and resubmitted to the EventLog via a regular CORBA interface.

Listing Four includes an example of the Perl module that is a client to the UDP bridge. This module, in effect, is an interface to the EventLog for any Perl script.

The last resort for a programming environment that cannot support even UDP (Windows NT batch files, for instance) is a command-line utility that can. In this case, a batch script executes a command-line utility that sends a UDP message, and the message, in turn, is translated to the CORBA call by the UDP bridge application. Listing Five provides an example of such utility, this one written in Perl (since there already is the client interface for Perl). The obvious question is: Why not write a command-line program that will make CORBA calls directly? You can, but it takes much longer to initialize and connect the ORB than to send a UDP message.

Server Side

The EventLogService is implemented in Java; see Listing Six. Leaving C++ versus Java flames aside, there is one Java feature that makes it a top choice for this job — run-time typing that allows, in this situation, for flexible event dispatch (portability is another positive factor). By default, Listing Six dumps all the messages it receives into a text file unless there is a custom event handler. All event handlers should implement the LogCapable interface given in Listing Seven.

On start-up, the service receives a list of event types and their respective handlers. For example:

java EventLogService DISCARD=BlackHole [TYPE=HandlerClass]

where DISCARD is an event type that will be processed by the handler class BlackHole, which simply ignores everything it receives; see Listing Eight. These handler classes get instantiated and loaded into the dispatch table. At run time, the handlers are invoked according to the event types. An unlimited number of event handlers may be created: from a simple e-mail sender to the SNMP trap generator.

Conclusion

The EventLogService facility I've presented here combines several techniques to achieve programming language independence, portability, and flexible event routing. The solution is CORBA based but, in addition to IDL mappings, its language support is extended by the UDP bridge and the companion command-line utility. The portability is provided by the tool selection: The server side of the facility will run anywhere where Java and CORBA are supported and the minimal requirement for a client is to be able to send a UDP message. The run-time features of Java help a lot in a flexible dispatch; a similar functionality would have been a lot quirkier in a strongly typed language.

Acknowledgment

Thanks to Alexander Galyaev and Xiaotie Shen who greatly contributed to the development of the concepts discussed in this article.

DDJ

Listing One

public class Log extends Exception
{
  private static EventLog remoteLog = null; // Stub for remote service
  private static String hostName = ""; // How this machine is called
  static
  {
    try
    {
      hostName = java.net.InetAddress.getLocalHost().getHostName();
    }
    catch(java.net.UnknownHostException e){}
  }
  public Log() { super(); }
  public Log(String message) { super(message); }
  public Log(String eventSource, String messageType, String message)
  {
    super(message);
    Log.log(eventSource, messageType, message);
  }
  // Actually logs an event.
  public static void log(String eventSource, String messageType, 
                                                    String aMessage)
  {
    if( null == remoteLog )
    {
      org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(new String[0], null);
      remoteLog = EventLogHelper.bind(orb);
    }
    try
    {
      synchronized(remoteLog)
      {
        remoteLog.log(hostName, messageType, eventSource, aMessage);
      }
    }
    catch(Throwable e) {}
  }
}

Back to Article

Listing Two

public class TestEventLogService
{
  public static void main(String args[])
  {
    try
    {
      // Just log some stuff as we go ...
      Log.log("main", "UNKNOWN", "Message of unknown type");
      Log.log("main", "DISCARD", "Message to be discarded");
      // Or throw it as an exception
      throw new Log("main", "ERROR", "Error message");
    }
    catch(Exception e) {}
  }
}

Back to Article

Listing Three

import java.util.*;
import java.net.*;
public class UDPBridge
{
  public static final int PORT=16500;
  public static final String DOMAIN="ALL-SYSTEMS.MCAST.NET";
  public static final String DELIMITER="|";
  private static byte[] buf = new byte[65509];

  static public void main(String[] args) 
  {
    try
    {
      // Create a UDP socket
      MulticastSocket ms = new MulticastSocket(PORT);
      ms.joinGroup(InetAddress.getByName(DOMAIN));
      // Initialize CORBA connection
      org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(new String[0], null);
      EventLog remoteLog = EventLogHelper.bind(orb);

      while(true) 
      {
        // Receive the message
        DatagramPacket dp = new DatagramPacket(buf, buf.length);
        ms.receive(dp);

        // Parse the event
        String s = new String(dp.getData(), 0, 0, dp.getLength());
        StringTokenizer t = new StringTokenizer(s, DELIMITER);

        // Send the event message to the Event Log Service
        String remoteHost = dp.getAddress().getHostName();
        remoteLog.log(remoteHost,
           t.nextToken(), t.nextToken(), t.nextToken());
      }
    }
    catch(Exception e) {}
  }
}

Back to Article

Listing Four

package Log;
use Socket;
use Sys::Hostname;

my($iaddr, $proto, $paddr, $host, $port);

$port = 16500;
$host = "ALL-SYSTEMS.MCAST.NET";

$iaddr = gethostbyname($host);
$proto = getprotobyname('udp');
$paddr = sockaddr_in($port, $iaddr);

socket(SOCKET,PF_INET, SOCK_DGRAM, $proto) or die "Socket: $!";
connect(SOCKET, $paddr) or die "Connect: $!";
sub log
{
  defined(send(SOCKET, join('|', @_), 0)) or die "Send: $!";
}
1;

Back to Article

Listing Five

use Log;
Log::log($ARGV[0], $ARGV[1], $ARGV[2]);

Back to Article

Listing Six

import java.io.*;
import java.util.*;
import java.text.*;

public class EventLogService extends _EventLogImplBase
{
  private static PrintWriter out = null;
  private static DateFormat dateFormat =
       new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
  private static Hashtable dispatcher = new Hashtable();
  public synchronized void log(String aHost,
       String aType, String aSource, String aMessage)
  {
    try
    {
      // Dispatch according to the type
      LogCapable handler = (LogCapable) dispatcher.get(aType);
      if( null != handler )
      {
        handler.log(aHost, aType, aSource, aMessage);
        return;
      }
      // Write the log string to the log file
      // for all types without custom handlers 
      out.println(aHost+"|"+ dateFormat.format(new Date())+
             "|"+aSource+"|"+aType+"|"+aMessage);
    }
    catch(Exception e) {}
  }
  public EventLogService(String name) {super(name);}
  // This method will start the event log service.
  public static void main(String[] args)
  {
    try
    {
      // Create the type dispatch table
      for(int i=0; i<args.length; i++)
      {
        String typeName = args[i].substring(0, args[i].indexOf('='));
        String className = args[i].substring(args[i].indexOf('=')+1);
        LogCapable handler = (LogCapable)
           Class.forName(className).newInstance();
        dispatcher.put(typeName, handler);
      }
      // Initialize the ORB.
      org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args, null);
      // Create the service object.
      EventLogService service = new EventLogService("log");
      // Open the log file
      out = new PrintWriter(new BufferedWriter(
               new FileWriter("logfile.log")), true);
      // Initialize the BOA.
      com.inprise.vbroker.CORBA.BOA boa = 
        ((com.inprise.vbroker.CORBA.ORB)orb).BOA_init();
      boa.obj_is_ready(service);
      // Wait for incoming requests
      boa.impl_is_ready();
    }
    catch(Throwable e) {}
  }
}

Back to Article

Listing Seven

public interface LogCapable
{
  public void log(String aHost, String aType, String aSource, 
                                                    String aMessage);
}

Back to Article

Listing Eight

public class BlackHole implements LogCapable
{
  public synchronized void log(String aHost,
            String aType, String aSource, String aMessage)
  {
    // Nothing!
  }
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.