Saturday, February 23, 2008

Performance Logging using Servlet Filter


In a J2EE application, performance is one of the crucial factors for the success of the application. Ongoing monitoring of the performance of the application is an activity during the entire lifetime of an application In most of cases, an external performance monitoring tool is used to test the application performance in a production like environment.

In an enterprise application scenario, with the application interacting with the various back end systems and the database, performance becomes the major challenge. They are

  • Total time taken by the application to process the request
  • Calculation of time spent in each of the tier.
  • Time spent in the integration tier to talk to different back end or database
  • Capturing the data in the production environment.

In order to address these issues, one of the solutions is to have a performance monitoring as a part of application which can be enabled or disabled based on the preference. This article will help the developer in implementing a simple performance logging using log4J, Servlet filter and Weblogic application server.

Architecture

This above figure shows the basic architecture for implementing performance logging with the help of log4j and filter Servlet. The architecture displayed above consists of the following components

  • Filter Servlet
  • Log4J.xml
  • Web.xml

Filter Servlet

A filter is like a preprocessor of the request and postprocessor of the response coming to a Servlet engine. Filters can be applied to any resources served by a Servlet engine, whether it's flat HTML, graphics, a JSP page, Servlet, or whatever. Filters are essentially a server plug-in that works with any Servlet container compliant with version 2.3 or later of the Servlet specification.

Log4j

Log4j is a widely used API for logging messages in an application. Log4j is configured with the help of the configuration file.

The log4j provides the flexibility of defining appender for an application based on the needs. This appender can be configured using the configuration file to log the messages in the file.

The next section describes the implementation of the performance filter.

Implementation

The implementation is defined in a step by step manner as defined below.

1. Define the log4j appender

2. Define the parameters in web.xml

3. Define the logging parameters

4. Define the Filter Servlet

5. Package and Deploy

Performance Appender

A custom appender to be defined in the log4j.xml which would log all the messages pertaining to the performance in this file.

The description of the parameters specified in Performance Appender is as follows.

Parameter

Description

File

Name of the file where the performance data should be stored

Append

File to be appended every time

MaxFileSize

Maximum size of the file

The log4J.xml file can be stored in the application server domain home. Include the following line in the weblogic startup script so that the application server can locate the file and load it appropriately.

set LOG4J_CONFIG_FILE=%DOMAIN_HOME%/log4j.xml

Web.xml

In order control the logging of the performance filter we can define the following

Parameter

Value

enablePerformanceHeaders

1 = True

0 = False

enablePerformanceLogging

1 = True

0 = False

The enablePerformanceHeaders would ensure that the performance related parameters are also logged in the response header which can be read by any program outside this application used for performance testing or monitoring.

The enablePerformanceLogging would be used for enabling or disabling of the performance logging.

Logging Parameters

The logging parameters can be defined based on the application requirement working. In case the application caters to variety of the backend and services, it would be useful to create more number of parameters. For the current example, we could consider the following

Parameter

Description

responseId

A unique id for a given request/response for the application instance

remoteAddress

Client IP

userID

User ID used in the application

callTime

Total time spent in the application from the time the request came and the response given.

response Time1

A long variable defining the time spent in the business layer

response Time2

A long variable defining the time spent in the Database or the integration layer

extraData

Any additional data required

These responseTime1, responseTime2 and extra data parameters need to be populated in the application. In order to ensure filter receives this data, the application will be logging this parameter as part of the response object.

Performance Filter

This filter is used as pre-processor and post processor for the application.

/**
* This filter is to log the time taken to process each request

*/

package com.hp.presentation.common.filters;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class PerformanceFilter implements Filter {

// Used for performance logging to a Log4J appender
private static Log perfLog = LogFactory.getLog("performance.perflog");
private FilterConfig config = null;

// Initialiase the FilterConfig object
public void init(FilterConfig filterConfig) throws ServletException {
this.config = filterConfig;
}

// destroy this instance
public void destroy() {
this.config = null;
}

// This method is called for every hit to the server
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
if (!perfLog.isInfoEnabled() || (req.getAttribute("FIRST_URI") != null))
{
chain.doFilter(req, res);
return;
}

// Get the current time in milli seconds before serving the request
long before = System.currentTimeMillis();

// Make sure that it's a HTTP Request
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

// used to synchronize the testtool with the logfile
String responseID = generateID();
If (enablePerformanceHeaders) {
response.setHeader("BEP_RESPONSE_ID", responseID);
}

// Initialise a string to hold the request URI
String name = request.getRequestURI();

// Used to filter out forwards
request.setAttribute("FIRST_URI", name);
String remoteAddress = (String) request.getHeader("WL-Proxy-Client-IP");
if (remoteAddress == null) {
remoteAddress = request.getRemoteAddr();
}

// Continue with the Filter for the next request
chain.doFilter(request, res);
//logger.debug("ExtraPerfData***"+extraPerfData);
String responseTime1 = "";
String responseTime2 = "";
String extraPerfData = "";
if ((request.getAttribute("RESPONSETIME1")) != null) {
responseTime1 = (String) request.getAttribute("RESPONSETIME1");
}
if ((request.getAttribute("RESPONSETIME2")) != null) {
responseTime2 = (String) request.getAttribute("RESPONSETIME2");
}
if ((request.getAttribute("EXTRAPERFDATA")) != null) {
extraPerfData = (String)request.getAttribute("EXTRAPERFDATA");
}
// Get the current time in milli seconds after serving the request

long after = System.currentTimeMillis();
Long callTime = new Long(after - before);

// Log to performance log (File, JMS Topic, Chainsaw etc.)
if (perfLog.isInfoEnabled()) {
StringBuffer strBuff = new StringBuffer(132);
strBuff.append(responseID);
strBuff.append(";");
strBuff.append(remoteAddress);
strBuff.append(";");
strBuff.append(name);
strBuff.append(";");
strBuff.append(callTime);
strBuff.append(";");
strBuff.append(responseTime1);
strBuff.append(";");
strBuff.append(responseTime2);
strBuff.append(";");
strBuff.append(extraPerfData);
perfLog.info(strBuff);
}
}

/**
* Generates simple ID for this call
* Only unique in this application instance and not garanteed after restart.
* Generation of UUID only available in Java 1.5!
*/
private static int idCounter = 0;
private static String loadTime =
(new Long(System.currentTimeMillis())).toString().substring(7);

public synchronized String generateID() {
return loadTime + idCounter++;
}
}

Usage of Performance logging in Application

This Performance login service can be used in the application as follows with minimum changes.

  • Create log4j.xml file with Appender and configure it in the Weblogic Application Server as explained earlier.
  • Copy the Servlet code as specified above.
  • Set the parameters in the web.xml.
  • In case the application specific parameters to be captured, add it accordingly in the application as the extraData parameter
  • Capture the Response Time in Application. (Not doing this will only result in the one single call time logged)

Assumption: log4j is already configured with the application.