Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Track Your Apps with the Open Source Logging Framework, Log4j : Page 5

As many developers can attest, logging is one of the most accessible ways to monitor the health and performance of your Java applications. Find out how Log4j, a logging framework from Apache, can handle both simple and advanced (multi-app, multi-machine) logging scenarios.

The JMSQueueAppender
Today's distributed applications typically have software components residing on different physical machines. One could go about logging to individual files for each software component. However, it would be simpler to manage one centralized log file. To accomplish this, I will use asynchronous messaging and JMS. (For more on JMS, please read my earlier article, "Master JMS with OpenJMS.")

Author's Note: Log4j is designed to be thread safe; it allows for multiple applications/threads to log to the same log file. Therefore, JMS is not needed on a single machine set up.

Figure 3. Points to Point: The JMS provider manages a message queue that handles log entries from multiple points.
Log4j ships with a JMSAppender, which publishes messages to topics using a publish/subscribe messaging model. However, to make sure that log events are kept in sequence in a consolidated log file, a point-to-point set up is more desirable. I will thus leverage the Log4j JMSQueueAppender (contributed to the Apache Log4j project by Jamie Tsao).

The JMSQueue appender hasn't officially found its way into the final Log4j project code. However, you can get your hands on it by looking in the "contribs\Jamie Tsao" directory of the Log4j extract. Figure 3 depicts a typical setup in which each software component can use the JMSQueueAppender of Log4j to write to a JMS provider using a point-to-point messaging model. In essence, each software component that wishes to write to a consolidated log represents a 'point.' All 'points' write to a designated queue on our messaging provider (the end point in our points-to-point set up). Ultimately, a "collector" application can be used to gather the log messages written to the JMS provider's queues.

To demonstrate use of the JMS Appender, I'll use OpenJMS as the JMS provider. Log4JJMSQueueAppenderUsageExample is a class included in the sample code for this article. The code below is extracted from that class. As you can see, use the setters of the JMSQueueAppender to establish a connection to the queue named "queue1," which is a queue that the OpenJMS install preconfigures by default.

// create JMSQueueAppender // first specify connection properties to the JMS provider JMSQueueAppender jmsQueueAppender = new JMSQueueAppender(); jmsQueueAppender.setInitialContextFactory("org.exolab.jms.jndi.InitialContextFactory"); jmsQueueAppender.setProviderUrl("rmi://localhost:1099/"); jmsQueueAppender.setQueueConnectionFactoryBindingName("JmsQueueConnectionFactory"); // specify the queue we want to write to jmsQueueAppender.setQueueBindingName("queue1"); // establish the connection to the queue jmsQueueAppender.activateOptions();

Because the log file being produced will be a composite of logs from different applications that might reside on different machines, it is a good idea to include this origination information in the log statements (so you know where a particular log came from). I did this by using the code:

java.net.InetAddress localMachine = java.net.InetAddress.getLocalHost(); String hostAddress = localMachine.getHostAddress());

When I log an event, I simply include this information in my log:

myLogger.fatal(hostAddress + " - I have died of thirst");

Author's Note: To aid your understanding, it is a good idea to have a couple of applications (preferably on different physical machines in a network) that will write to your logs.

After putting messages on the JMS provider's queue, as Figure 3 depicts, you still need a "sink" / collector application to consume the messages from the JMS provider and write them to a log file. Log4j ships with a sink application that uses the pub/sub messaging model. Because we are using the point-to-point messaging model, we need to build our own "sink."

I prebuilt a sink for you with the class MessageSink (see the sample code zip file). This message consumption application listens asynchronously to the queue that the JMSQueueAppender in our set up wrote to (queue1). (See the section named 'Asynchronous Retrieval' in my earlier article on OpenJMS.) I set up my connection to the queue of interest in my constructor, the code of which is shown below:

// create a JNDI context Hashtable properties = new Hashtable(); properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.exolab.jms.jndi.InitialContextFactory"); properties.put(Context.PROVIDER_URL, "rmi://localhost:1099/"); Context context = new InitialContext(properties); // retrieve topic connection factory QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory) context.lookup("JmsQueueConnectionFactory"); // create a queue connection queueConnection = queueConnectionFactory.createQueueConnection(); // create a queue session // set transactions to false & set auto-ack of receipt of messages queueSession = queueConnection.createQueueSession( false, Session.AUTO_ACKNOWLEDGE); // retrieve queue queue = (Queue) context.lookup("queue1"); // create a queue receiver and associate to the retrieved queue queueReceiver = queueSession.createReceiver(queue); // associate message listener queueReceiver.setMessageListener(this); // start delivery of incoming messages queueConnection.start();

The MessageSink class I provide implements the javax.jms.MessageListener interface and therefore is required to implement the onMessage method, via which it pulls messages off the message queue. In our onMessage message (shown below) is where I write messages to a log file. The callAppenders message allows me have my LoggingEvent sent to all the associated appenders I have registered. By analyzing the code of the MessageSink class, you'll see that I have registered two appenders, a RollingFileAppender and a ConsoleAppender.

// process incoming queue messages public void onMessage(Message message) { LoggingEvent event; try { String messageText = null; if (message instanceof ObjectMessage) { ObjectMessage objectMessage = (ObjectMessage) message; event = (LoggingEvent) objectMessage.getObject(); myLogger.callAppenders(event); } } catch (JMSException e) { e.printStackTrace(); } }

To write to a log file I used (surprise, surprise) another Log4j appender, namely the org.apache.log4j.RollingFileAppender, which I created using the source below:

// create a rolling file appender RollingFileAppender rollingFileAppender = null; try { // create a rolling file appender that writes to // c:\combinedlog4joutput.log // the true argument of the constructor states that the // file should be appended to, not written over rollingFileAppender = new RollingFileAppender(myLayout,"c:\\combinedlog4joutput.log",true); // set the rollover size to 100K rollingFileAppender.setMaxFileSize("100KB"); } catch(Exception e) {e.printStackTrace();}

By putting the example code to work, you should be able to see that your consolidated log file will contain the log events of all the software components which you have configured as participants in the setup.

Comment and Contribute








