IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology | XML
developerWorks
Instant logging: Harness the power of log4j with Jabber
code354 KBe-mail it!
Contents:
Overview of log4j
Understanding appenders
Under the hood
Writing an IM-based appender
Beyond the basics
Putting it all together
Conclusion
Resources
About the authors
Rate this article
Related content:
Jabber
LogKit
Magic with Merlin: Exceptions and logging
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Learn how to extend the log4j framework with your own appenders

Level: Advanced

Ruth Zamorano (mailto:ruth.zamorano@orange-soft.com?cc=&subject=Instant logging: Harness the power of log4j with Jabber), Software architect, Orange Soft
Rafael Luque (mailto:rafael.luque@orange-soft.com?cc=&subject=Instant logging: Harness the power of log4j with Jabber), CTO, Orange Soft

12 August 2003

Not only is logging an important element in development and testing cycles -- providing crucial debugging information -- it is also useful for detecting bugs once a system has been deployed in a production environment, providing precise context information to fix them. In this article, Ruth Zamorano and Rafael Luque, cofounders of Orange Soft, a Spain-based software company specializing in object-oriented technologies, server-side Java platform, and Web content accessibility, explain how to use the extension ability of log4j to enable your distributed Java applications to be monitored by instant messaging (IM).

No matter how many well-designed test cases you write, even the smallest application will hide one or more bugs once it's deployed in the production environment. While test-driven development and QA practices improve code quality and provide confidence in the application, when a system fails, developers and system administrators need contextual information about its execution. With the appropriate information, they can identify the nature of the problem and fix it quickly, saving time and money.

Monitoring distributed applications requires the ability to log to remote resources -- typically a central log server or system administrator's computer. The log4j environment provides a collection of appenders suitable for remote logging, such as SocketAppender, JMSAppender, and SMTPAppender. In this article, we're going to show you a new remote-class appender: IMAppender.

Let's start with a brief review of log4j and then move into an in-depth look at appenders. Naturally, the best way to understand appenders is to try to write one, so in the last section we'll implement an example IM (instant messaging) appender to show how the AppenderSkeleton class works.

You should be familiar with the log4j framework. For more information about log4j, see Resources later in this article.

Overview of log4j
The log4j framework is the de facto logging framework written in the Java language. As part of the Jakarta project, it is distributed under the Apache Software License, a popular open source license certified by the Open Source Initiative (OSI). The log4j environment is fully configurable programmatically or through configuration files, either in properties or XML format. In addition, it allows developers to filter out logging requests selectively without modifying the source code.

The log4j environment has three main components:

  • loggers: Control which logging statements are enabled or disabled. Loggers may be assigned the levels ALL, DEBUG, INFO, WARN, ERROR, FATAL, or OFF. To make a logging request, you invoke one of the printing methods of a logger instance.

  • layouts: Format the logging request according to the user's wishes.

  • appenders: Send formatted output to its destinations.

Understanding appenders
The log4j framework allows you to attach multiple appenders to any logger. You can add (or remove) appenders to a logger at any time. There are several appenders distributed with log4j, including:

  • ConsoleAppender
  • FileAppender
  • SMTPAppender
  • JDBCAppender
  • JMSAppender
  • NTEventLogAppender
  • SyslogAppender

You can also create your own custom appender.

One of log4j's main features is its flexibility. Unfortunately, little documentation exists on how to write your own appender. One way to learn is to analyze available source code and then try to infer how appenders work -- a task we can help you with.

Under the hood
All appenders must extend the org.apache.log4j.AppenderSkeleton class, an abstract class that implements the org.apache.log4j.Appender and org.apache.log4j.spi.OptionHandler interfaces. The UML class diagram of the AppenderSkeleton class looks like Figure 1:

Figure 1. AppenderSkeleton UML class diagram
AppenderSkeleton UML class diagram

Let's examine the methods of the Appender interface implemented by the AppenderSkeleton class. As Listing 1 indicates, nearly all methods in the Appender interface are setter and getter methods:

Listing 1. Appender interface

package org.apache.log4j;

public interface Appender {
    void addFilter(Filter newFilter);                    
    void clearFilters() ;                                               
    void close();                                                        
    void doAppend(LoggingEvent event);            
    ErrorHandler getErrorHandler();     
    Filter getFilter();         
    Layout getLayout();          
    String getName();       
    boolean requiresLayout();        
    void setErrorHandler(ErrorHandler errorHandler);        
    void setLayout(Layout layout);       
    void setName(String name);         
}

These methods handle appender properties like the following:

  • name: Appenders are named entities so there is a setter/getter for its name.

  • layout: Appenders may have a Layout associated with them, so there is another setter/getter method for the layout. Note that we said may and not must. The reason is that some appenders don't require a layout. A layout manages format output -- that is, it returns a String representation of the LoggingEvent. On the other hand, JMSAppender sends the event serialized, so you're not required to attach a layout with it. If your custom appender doesn't require a layout, the requiresLayout() method must return false to prevent log4j from complaining about missing layout information.

  • errorHandler: Another setter/getter method exists for ErrorHandler. Appenders may delegate their error handling to an ErrorHandler object -- an interface in the org.apache.log4j.spi package. There are two implementing classes: OnlyOnceErrorHandler and FallbackErrorHandler. The OnlyOnceErrorHandler implements log4j's default error handling policy, which consists of emitting a message for the first error in an appender and ignoring all following errors. The error message is printed on System.err. FallbackErrorHandler implements the ErrorHandler interface such that a secondary appender may be specified. This secondary appender takes over if the primary appender fails. The error message is printed on System.err, then logged in the new secondary appender.

There are other methods to manage filters (such as the addFilter(), clearFilters(), and getFilter() methods). Even though log4j has several built-in ways to filter log requests (such as repository-wide level, logger level, and appender threshold), it is also very powerful in its approach to using custom filters.

An appender can contain multiple filters. Custom filters must extend the org.apache.log4j.spi.Filter abstract class. This abstract class requires filters to be organized in a linear chain. The decide(LoggingEvent) method of each filter is called sequentially, in the order it was added to the chain. Custom filters are based on ternary logic. The decide() method must return one of the DENY, NEUTRAL, or ACCEPT integer constants.

Besides setter/getter methods and the ones related with filters, there are two other methods: close() and doAppend(). The close() method releases any resources allocated within the appender, such as file handlers, network connections, and so on. When coding your custom appender, be sure you implement this method so that when your appender is closed, its closed field is set to true.

The doAppend() method, shown in Listing 2, follows the Gang of Four Template Method design pattern (see Resources). This method provides a skeleton of an algorithm, deferring some steps to subclasses.

Listing 2. Actual source code of the doAppend() method

public synchronized void doAppend (LoggingEvent event) {

    if (closed) {         // step 1
     LogLog.error("Attempted to append to closed appender [" + name + "].");
     return;
    }
    if ( !isAsSevereAsThreshold (event.level) ) {        // step 2
        return;
    }
    Filter f = this.headFilter;        // step 3
    FILTER_LOOP:
    while ( f != null) {
        switch ( f .decide(event) ) {
        case Filter.DENY: return;
        case Filter.ACCEPT: break FILTER_LOOP;
        case Filter.NEUTRAL: f = f.next;
        }
    }
    this.append(event);        // step 4
}

As Listing 2 shows, the algorithm:

  1. Checks whether the appender is closed. It is a programming error to append to a closed appender.
  2. Checks whether the logging event is below the threshold of the appender.
  3. Checks whether filters attached to the appender, if any, deny the request.
  4. Invokes the append() method of the appender. This step is delegated to each subclass.

We have described the methods and properties that AppenderSkeleton inherits from Appender. Let's see why AppenderSkeleton implements the OptionHandler interface. OptionHandler contains only one method: activateOptions(). This method is invoked by a configurator class after calling setter methods for properties. Some properties depend on each other so they cannot be activated until all of them have been loaded, such as in the activateOptions() method. This method is a mechanism for developers to perform whatever tasks were necessary before the appender became activated and ready.

In addition to all the methods mentioned, look again at Figure 1. Notice that AppenderSkeleton provides a new abstract method (the append() method) and a new JavaBeans property (threshold). The threshold property is used to filter logging requests by the appender, with only requests over the threshold handled. We mentioned the append() method before when we talked about the doAppend() method. It is an abstract method that your custom appender must implement because the framework calls it within the doAppend() method. The append() method is one of the hooks of the framework.

Now that we've seen all the methods available in the AppenderSkeleton class, let's see what's happening behind the scenes. Figure 2 illustrates the life cycle of an appender object inside log4j:

Figure 2. Life cycle diagram for appenders
Life cycle of an appender object inside log4j

Let's walk through the diagram:

  • The appender instance does not exist. Perhaps the framework has not been yet configured.

  • The framework instantiates a new appender. This happens when the configurator classes parse an appender declaration in configuration scripts. The configurator classes call Class.newInstance(YourCustomAppender.class), which is the dynamic equivalent of calling new YourCustomAppender(). The framework does this so that the framework is not hard-coded to any specific appender name; the framework is generic and works with any appender.

  • The framework determines whether the appender requires a layout. If the appender doesn't require a layout, then configurators don't try to load layout information from configuration scripts.

  • Log4j configurator calls setter methods. The framework transparently handles appender's properties following JavaBeans naming conventions.

  • Configurator invokes the activateOptions() method. After all the properties have been set, the framework invokes this method. Programmers can activate properties here that have to be activated at the same time.

  • Appender is ready. At this point, the framework can call the append() method to handle a logging request. This method is invoked by the AppenderSkeleton.doAppend() method.

  • Finally, the appender is closed. When the framework is about to remove your custom appender instance, it calls your appender's close() method. close() is a cleanup method, which means you need to free all the resources you have allocated. It is a required method, and it takes no parameters. It must set the closed field to true, alerting the framework when an attempt to use a closed appender occurs.

Now that we've reviewed the concepts related to building your own appender, let's look at a complete case study including a real-world example appender.

Recipe for writing custom appenders

  1. Extend the AppenderSkeleton abstract class.

  2. Specify whether your appender requires a layout.

  3. If some properties must be activated simultaneously, do it within the activateOptions() method.

  4. Implement the close() method. It must set the value of the closed field to true. Remember to release any resources.

  5. Optionally, specify the default ErrorHandler object to use.

  6. Code the append() method. This method is responsible for appending the logging events and for calling error handlers if an error occurs.

Writing an IM-based appender
The code outlined in this article shows how you can extend the log4j framework to integrate IM features. It's designed to enable a log4j-compliant application to log its output onto IM networks. The IM appender actually works as a customized IM client. However, instead of System.out, files, or TCP sockets, it takes IM networks as the underlying output device.

To provide IM support, we don't need to reinvent the wheel when developing ad-hoc solutions. Instead, we're going to leverage a tool we consider the best of breed: Jabber. Jabber is an open, XML-based protocol for instant messaging and presence, developed by the Jabber community and supported by the non-profit Jabber Software Foundation.

We chose Jabber over other IM systems because it offers a wide variety of benefits, including its:

  • Open nature: Unlike other proprietary systems, Jabber's specifications and source code are freely available, allowing anyone to create Jabber implementations at no cost.

  • Simplicity: Jabber uses simple protocols based on XML as its standard data format, and follows the well-understood client/server architecture.

  • Interoperability with other IM systems: Jabber transport modules make it possible for Jabber users to reach other instant messaging systems such as AIM, Yahoo! Messenger, and ICQ.

  • Resource awareness: Jabber provides explicit support for multiple client access. The same user can connect simultaneously to the Jabber server with different clients (or resources), and messages will be routed properly to the best resource available.

Why log on to IM networks?
Logging is a good coding habit that developers must acquire, like writing unit tests, handling exceptions, or writing Javadoc comments. Logging statements inserted in a set of well-defined points of the code function as an auditing tool that provides useful information about the internal state of applications. Contrary to mainstream opinion, we think in many cases it's convenient to leave log statements in the production code. If you worry about computational cost, you must consider whether a small performance gain justifies removing logging capabilities from your application. In addition, log4j's flexibility lets you control logging behavior declaratively. You can establish a restrictive logging policy to reduce verbosity and improve performance.

Figure 3 shows the IMAppender usage scenario: A log4j application configured to use IMAppender logs its debugging data wrapped as an IM message. The instant message is routed over the Jabber company-wide network to the system administrator's Jabber address (notice that publicly available Jabber servers might not be reliable enough for production use). Thus, whenever system administrators need to check on the application's status, they simply load their favorite Jabber client and connect to the Jabber server. As the figure shows, the administrator can be reached through different devices. He may log in to the server using his PC at work, or when he is away from his desk, he may use a Jabber client running on a handheld device to check messages.

Figure 3. IMAppender usage scenario
IMAppender usage scenario

But why do you need an IM appender? The answer is that sending messages to an IM server allows you to monitor application behavior more easily with tools at your disposal (such as your Jabber clients).

IMAppender offers several advantages:

  • You get real-time notification -- what we call "instant logging".

  • One-to-one (chat) and one-to-many (group chat) modes are supported.

  • Jabber isn't just for desktop computers. Clients are being developed for wireless devices such as PDAs and mobile phones.

  • It's easy to log on or log off a chat room that an application is forwarding some of its log data to. It's more difficult to subscribe and unsubscribe to receive e-mails sent by SMTPAppender.

  • It's easy to secure by tunneling over a secure socket layer (SSL). Of course, you can encrypt e-mail, but Jabber over SSL is quick and easy.

Beyond the basics
IMAppender is modeled after the logging strategy of SMTPAppender, shipped with log4j. IMAppender stores logging events in an internal cyclic buffer and sends them as an instant message only when the logging request received triggers a user-specified condition. Optionally, users may provide a triggering event evaluator class. However, by default, delivery is triggered on events assigned the level ERROR or higher.

The number of logging events delivered in each message is limited by the buffer size. The cyclic buffer holds only the last bufferSize logging events, overflowing when it becomes full and throwing away the older events.

To connect to a Jabber server, IMAppender relies on Jive Software's Smack API. Smack is an open source, high-level library that handles the protocol details for communicating with Jabber servers. As a result, you don't need any special Jabber or XML expertise to understand the code.

The properties for IMAppender are summarized in Table 1:

Table 1. IMAppender properties

Property Description Type Required?
host The host name of the Jabber server. String Yes
port The port number of the Jabber server. int No, defaults to 5222
username Application's Jabber account username. String Yes
password Application's Jabber account password. String Yes
recipient Recipient's Jabber address. Jabber addresses, known as Jabber IDs, specify the user's Jabber domain following an "@" character, just as e-mail addresses do.

This property can hold either a chat address or a chatroom address. For example, you may specify a chat address like sysadmin@company.com, or you may want to send logging messages to a groupchat named "java-apps" on the conference.company.com groupchat server (for instance, java-apps@conference.company.com).
String Yes
chatroom Takes a boolean value. If true, the recipient value is taken as a groupchat address. If you set this option, you should also set the nickname option. By default, the recipient value is interpreted as a chat address. boolean No, defaults to false
nickname Taken into account only if the chatroom property is set. Otherwise, it's ignored.

Users can choose an arbitrary groupchat nickname used by the appender to join the groupchat. The nickname doesn't necessarily have anything to do with the Jabber username.
String No
SSL Used to secure connections with the Jabber server. boolean No, defaults to false
bufferSize The maximum number of logging events that can be kept in the cyclic buffer. int No, defaults to 16
evaluatorClass Takes as a value a string representing the fully qualified name of a class that implements the org.apache.log4j.spi. TriggeringEventEvaluator interface (in other words, a class that contains a custom triggering logic that overrides the default one). If this option is not specified, IMAppender uses an instance of the DefaultEvaluator class, which triggers a response to events assigned the level ERROR or higher. String No, defaults to DefaultEvaluator

Now let's take a closer look at the code. The IMAppender class follows the structure shown in Listing 3:

Listing 3. Overall structure of the IMAppender class

package com.orangesoft.logging;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.TriggeringEventEvaluator;

public class IMAppender extends AppenderSkeleton {

    private String host;
    private int port = 5222;
    private String username;
    private String password;
    private String recipient;
    private boolean chatroom = false;
    private String nickname;
    private boolean SSL = false;
    private int bufferSize = 16;
    protected TriggeringEventEvaluator evaluator;
    
    // Set/Get methods for properties
    
    public void setHost(String host) {
        this.host = host; 
    }
    
    public String getHost() { 
        return this.host; 
    }
    
    ...other set/get methods...
    
    // AppenderSkeleton callback methods
    
    public boolean requiresLayout() { ... }
    public void activateOptions() { ... }
    public void append(LoggingEvent event) { ... }
    public synchronized void close() { ... }
    
}

Notice the following about our appender:

  • The IMAppender class extends org.apache.log4j.AppenderSkeleton, which all custom appenders must do. IMAppender inherits common functionalities from AppenderSkeleton such as appender threshold and custom filtering.

  • The first part of our appender is straightforward. We have the appender's fields and set/get methods for each of them. Properties and method signatures obey the JavaBeans naming convention. Thus, log4j can analyze the appender using reflection, transparently handling the appender configuration. To save space, the snippet shows only setHost() and getHost() methods.

  • To complete our appender, we must implement the callback methods that the log4j framework calls to manage our appender: requiresLayout(), activateOptions(), append(), and close().

The log4j framework calls the requiresLayout() method to know whether the custom appender requires a layout. Notice that some appenders use a built-in format or don't format the events at all, so they don't require a Layout object. The IMAppender requires a layout, hence the method returns true, as shown in Listing 4:

Listing 4. The requiresLayout() method

public boolean requiresLayout() {
    return true;
}

Notice that AppenderSkeleton implements the org.apache.log4j.spi.OptionHandler interface (see Figure 1). AppenderSkeleton implements the single method of this interface, activateOptions(), as an empty method. Our IMAppender needs this method because of the interdependence between its properties. For example, the connection with a Jabber server depends on the Host, Port, and SSL properties, so IMAppender cannot establish a connection until these three properties have been initialized. The log4j framework calls the activateOptions() method to signal to the appenders that all the properties have been set.

The IMAppender.activateOptions() method activates the specified properties, (such as the Jabber host, port, bufferSize, and so on) by instantiating higher-level objects that depend on their values, as shown in Listing 5:

Listing 5. Properties are activated and become effective only after calling the activateOptions() method

protected org.apache.log4j.helpers.CyclicBuffer cb;

protected org.jivesoftware.smack.XMPPConnection con;
protected org.jivesoftware.smack.Chat chat;
protected org.jivesoftware.smack.GroupChat groupchat;

public void activateOptions() {
    try {        
        cb = new CyclicBuffer(bufferSize);
    
        if (SSL) {
            con = new SSLXMPPConnection(host, port);
        } else {
            con = new XMPPConnection(host, port);
        }

        con.login(username, password);
     
        if (chatroom) {
            groupchat = con.createGroupChat(recipient);
            groupchat.join(nickname != null ? nickname : username);
        } else {
            chat = con.createChat(recipient);
        }
                       
    } catch (Exception e) {
        errorHandler.error("Error while activating options for appender 
          named [" + name + "]", 
            e, ErrorCode.GENERIC_FAILURE);
    } 
}

The activateOptions() method completes the following tasks:

  • Builds a maximum cyclic buffer of bufferSize events. We use an instance of org.apache.log4j.helpers.CyclicBuffer, a helper class shipped with log4j that provides the buffer's logic.

  • Smack's XMPPConnection class creates a connection to the XMPP (Jabber) server specified by the host and port properties. To create an SSL connection, we use the SSLXMPPConnection subclass.

  • Most servers require you to log in before performing other tasks, so we log in to the Jabber account defined by the username and password properties, invoking the XMPPConnection.login() method.

  • Once logged in, we create a Chat or GroupChat object, a function of the chatroom value.

Once the activateOptions() method returns, the appender is ready to handle logging requests. The append() method, shown in Listing 6, invoked by AppenderSkeleton.doAppend() does most of the real appending work.

Listing 6. append() performs actual output operations

public void append(LoggingEvent event) {

    // check pre-conditions
    if (!checkEntryConditions()) {
        return;
    }

    cb.add(event);
    if (evaluator.isTriggeringEvent(event)) {
        sendBuffer();
    }
}

protected  boolean checkEntryConditions() {
    if ((this.chat == null) && (this.groupchat == null)) {
        errorHandler.error("Chat object not configured");
        return false;
    }

    if (this.layout == null) {
     errorHandler.error("No layout set for appender named [" + name + "]");
     return false;
    }
    return true;
}

The first statement in the append() method determines whether it makes sense to attempt to append. The checkEntryConditions() method checks whether there is a Chat or GroupChat object available to append the output and also whether there is a Layout object to format the incoming event object. If these preconditions are not satisfied, then append() outputs a warning message and returns without proceeding. The next statement adds the event to the cyclic buffer instance cb. Then, the if statement submits the logging event to the evaluator, a TriggeringEventEvaluator instance. If evaluator returns true, it means that event matches the triggering condition, and sendBuffer() is invoked.

Listing 7 shows the sendBuffer() method's code:

Listing 7. The sendBuffer() method

protected void sendBuffer() {
    try {
        StringBuffer buf = new StringBuffer();
            
        int len = cb.length();
        for (int i = 0; i < len; i++) {
            LoggingEvent event = cb.get();
            buf.append(layout.format(event));
            
            // if layout doesn't handle exceptions, the appender has 
            // to do it
            if (layout.ignoresThrowable()) {
                String[] s = event.getThrowableStrRep();
                if (s != null) {
                    for (int j = 0; j < s.length; j++) {
                        buf.append(LINE_SEP);
                        buf.append(s[j]);
                    }
                }
            }
        }

        if (chatroom) {
            groupchat.sendMessage(buf.toString());
        } else {
            chat.sendMessage(buf.toString());
        }

    } catch(Exception e) {
        errorHandler.error("Could not send message 
          in IMAppender [" + name + "]", 
            e, ErrorCode.GENERIC_FAILURE);
    }
}

The sendBuffer() method sends the contents of the buffer as an IM message. The method loops through the events kept in the buffer, formatting each one by invoking the format() method of the layout object. The string representation of the events is appended to a StringBuffer object. Finally, sendBuffer() calls the sendMessage() method of the chat or groupchat object, sending the message.

Notice the following:

  • The AppenderSkeleton.doAppend() method, which invokes append(), is synchronized, so sendBuffer() already owns the monitor for the appender. This frees us from needing to synchronize on cb.

  • Exceptions provide extremely useful information. For this reason, if the layout specified ignores the throwable object contained within a LoggingEvent object, the custom appender developer must output the exception information included in the event. If the layout ignores throwable objects, then the layout's ignoresThrowable() method should return true, and sendBuffer() can use the LoggingEvent.getThrowableStrRep() method to retrieve a String[] representation of the throwable information contained within the event.

Download the source code
All the required JAR files to execute the sample -- instantlogging.jar, smack-1.1.0.jar, and log4j-1.2.8.jar -- are included with this article's source code under the lib/ directory. Download the zip archive from Resources.

Putting it all together
We'll wrap up by showing you IMAppender in action. We'll use a fairly simple application called com.orangesoft.logging.example.EventCounter, shown in Listing 8. The sample application takes two arguments in the command line. The first argument is an integer corresponding to the number of logging events to generate. The second argument must be a log4j configuration file name in properties format. The application always ends with an ERROR event, which triggers the delivery of an IM message.

Listing 8. EventCounter sample application

package com.orangesoft.logging.examples;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;


/**
 * Generates the number of logging events indicated by the first 
 * argument value. The application ends with an ERROR level event 
 * to trigger the IMAppender action.
 */

public class EventCounter {

    private static Logger logger = Logger.getLogger(EventCounter.class);

    public static void main(String args[]) {

        int numEvents = Integer.parseInt(args[0]);    
        String log4jConfigFile = args[1];

        PropertyConfigurator.configure(log4jConfigFile);

        for (int i = 1; i <= numEvents; i++) {
            logger.info("Event #" + i);
        }
        
        logger.error("This error event triggers the delivery", 
            new Exception("This is a mock exception"));
    }
    
}

We can use a configuration file like the one shown in Listing 9:

Listing 9. A sample IMAppender configuration file

log4j.rootLogger = ALL,im

log4j.appender.im = com.orangesoft.logging.IMAppender

log4j.appender.im.host = JABBER_SERVER (e.g. jabber.org)
log4j.appender.im.username = APP_JABBER_ACCOUNT_USERNAME
log4j.appender.im.password = APP_JABBER_ACCOUNT_PASSWORD
log4j.appender.im.recipient = YOUR_JABBER_ADDRESS (e.g. foobar@jabber.org)

log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern = %n%r [%-5p] %M:%L - %m

The above configuration script adds IMAppender to the root logger, so each logging request received will be dispatched to our appender.

Before trying out the sample application, be sure to set the host, username, password, and recipient properties to values appropriate to your environment. The following command will launch the EventCounter application:


java com.orangesoft.logging.examples.EventCounter 100 
eventcounter.properties

When run, EventCounter will log 100 events according to the logging policy set by eventcounter.properties. An IM message then pops up on the recipient's screen. Figures 4, 5, and 6 shows the resulting message received by Jabber clients on different platforms:

Figure 4. Screenshot of the message received by a Jabber client on Windows (Rhymbox)
Windows Jabber client (Rhymbox)

Figure 5. Screenshot of the message received by a Jabber client on Linux (PSI)
Linux Jabber client (PSI)

Figure 6. Screenshot of the message received by a Jabber client on a Pocket PC (imov)
Pocket PC Jabber client (imov)

Notice that EventCounter generated 100 events. However, given that the default size of the IMAppender buffer is 16, the recipient should receive an IM message containing only the last 16 events. As you can see, the exception info contained in the last event (message and stack trace) has been correctly transmitted.

This example application simply showcases a very small use of the IMAppender, so explore and have fun!

Conclusion
The log4j network appenders, SocketAppender, JMSAppender, and SMTPAppender, already provide mechanisms to monitor Java-distributed applications. However, several factors make IM a suitable technology for remote logging in real-time. In this article, we've covered the basics of extending log4j with your custom appenders, and we've seen the implementation of a basic IMAppender step by step. Many developers and system administrators can benefit from their use.

Resources

  • Download the source code of the IMAppender class, example application, and libraries required. Feel free to use and extend the source as necessary.

  • Get the latest log4j version -- including full-source code, class files, and documentation -- at the log4j project home. For more information, check out papers and presentations at the official log4j documentation page.

  • If your log4j developing needs go beyond this documentation, The Complete log4j Manual (QOS.ch, 2003) by Ceki Gülcü is an excellent reference book, covering both basic and advanced log4j features in detail.

  • The developerWorks Web services zone offers LogKit as a "component of the week" (August 2001). LogKit is the logging component of Jakarta's Avalon project.

  • Sun has completed a community process, named JSR 47, that defines a logging API for the Java platform. JSR 47 API and log4j are quite similar at the architectural level, but log4j has many features missing in JSR 47.

  • In "Magic with Merlin: Exceptions and logging" (developerWorks, December 2001), John Zukowski demonstrates how the new JDK 1.4 Logging API works.

  • The JavaWorld article "Logging on to Internet Relay Chat (IRC)" by Thomas E. Davis introduces a simple tool that lets your applications write output to IRC.

  • Gerhard Poul's "Jabber" (developerWorks, May 2002) shows how Jabber fits into today's e-business infrastructure.

  • Smack is an open source library provided by Jive Software for communicating with Jabber servers to perform instant messaging and chat.

  • Iain Shigeoka's Instant Messaging in Java (Manning, 2002) provides in-depth analysis of the various Jabber protocols.

  • The source of the Template Method design pattern is Design Patterns (Addison-Wesley, 1995) by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides -- also known as the Gang of Four (GoF).

  • You can also read this online write-up of the Template Method design pattern.

  • You'll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.

About the authors
Photo of Ruth ZamoranoRuth Zamorano is Senior Software Architect and cofounder of Orange Soft. Ruth has professional experience with Java programming, XML/XSLT, database design, and large J2EE projects. She currently leads development of Orange Soft's flagship mobile messaging product, PLASMA. Ruth earned a B.S. in Electrical Engineering from Politecnica University, Madrid. You can contact her at ruth.zamorano@orange-soft.com.


Photo of Rafael LuqueRafael Luque is Chief Technology Officer and cofounder of Orange Soft. Rafael has been part of the Internet industry since its early CGI days. He has worked on Java server-side projects over the past five years. Rafael is passionate about object-oriented programming, design patterns, Web accessibility, and the cutting edge of Java technology. He holds a B.S. in Electrical Engineering from Politecnica University, Madrid. You can reach him at rafael.luque@orange-soft.com.



code354 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Java technology | XML
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact