|
|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| Learn how to extend the log4j framework with your own
appenders
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
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:
- Checks whether the appender is closed. It is a programming error to
append to a closed appender.
- Checks whether the logging event is below the threshold of the
appender.
- Checks whether filters attached to the appender, if any, deny the
request.
- 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
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
- Extend the
AppenderSkeleton abstract
class.
- Specify whether your appender requires a layout.
- If some properties must be activated simultaneously, do it
within the
activateOptions() method.
- Implement the
close() method. It must set the
value of the closed field to true .
Remember to release any resources.
- Optionally, specify the default
ErrorHandler
object to use.
- 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
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)
Figure 5. Screenshot of the message received by a
Jabber client on Linux (PSI)
Figure 6. Screenshot of the message received by a
Jabber client on a Pocket PC (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 Ruth 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. |
Rafael 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.
|
|
|