You can use the web service appender to centralize logging to one place, allowing administrators to monitor and developers to debug any issues that might arise in a Service-Oriented Architecture (SOA) environment. The Web Service Appender is an extended Java class extending from Log4j's Appender class.
SOA is, by definition, a collection of services that communicate with each other. The services are self-contained and do not depend on the context or state of the other service, and they work within a distributed systems architecture. Within SOA, web services are used to process requests within a given transaction. These requests can be wrappers around legacy code, Enterprise Java Beans (EJBs), or Java classes. Using a logging method that is capable of placing log messages onto a central location helps isolate bugs and issues and to have a better understanding of process logic flow.
A mechanism that logs messages from specific modules or services to one central location would minimize detaching potential issues or problems.
This paper gives a general overview of how Log4j functions and an introduction on how to write a custom Log4j Appender. This particular Appender writes log messages to a specific web service.
Log4j is an open source logging library developed as a subproject of the Apache Software Foundation?s Logging Services Project. The library was based on a logging library developed at IBM® in the late 1990s; the first versions appeared in 1999. It is widely used in the open source community, and its architecture is built around the following three main concepts:
- Loggers
- Appenders
- Layouts
These concepts let you log messages according to the message type and message priority and control where messages end up and how they are formatted. Loggers are objects that your applications first call upon to initiate the logging of messages. When given a message to log, loggers generate LoggingEvents that wrap the message. The Logger objects then hand-off the LoggingEvent to their associated Appenders.
Appenders send the information contained by the LoggingEvent to specified output destinations, specified, in most cases, by the Log4j property file. Several Appenders exist within Log4j. You can also extend Appenders to allow support for other destinations, like XML files, consoles, and so forth.
In Log4j, LoggingEvents are assigned a level that indicates their priority. The default levels include the following:
- OFF: The highest possible rank and is intended to turn off logging
- FATAL: Designates very severe error events that will presumably lead the application to abort
- ERROR: Designates error events that might still allow the application to continue running
- WARN: Designates potentially harmful situations
- INFO: Designates informational messages that highlight the progress of the application at coarse-grained level
- DEBUG: Designates fine-grained informational events that are most useful to debug an application
- ALL: The lowest possible rank and is intended to turn on all logging
Loggers and Appenders are also assigned one of these levels, and only execute logging requests that have a level that is equal to or greater than their own. For example, if an Appender is assigned a level of INFO, and a logging request of DEBUG is issued, the Appender will not write the message for the given logging event.
The client log4j.properties file is the standard file that contains all of the Appenders that the service or module uses. The web Service Appender requires an endpoint attribute that specifis the logging service to use.
Listing 1 describes the web services client Log4j properties necessary to use the WebServiceAppender. The highlighted text indicates the Appender that will access the server side of the WebServiceAppender. The property file is a basic requirement to use Log4j. It lets you configure that application to use many Appenders and the logging severity. You can easily change the property file once the application goes into production or potential issues have been resolved.
Listing 1. Client Log4j property file
#set the level of the root logger
log4j.rootLogger = INFO, CONSOLE
#set own logger
log4j.logger.com.carmelouria.logging.test=CONSOLE
log4j.appender.CONSOLE=com.carmelouria.logging.WebServiceAppender
log4j.appender.CONSOLE.endpoint=
http://localhost:9080/log4j/services/LogAppenderService
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%p [%t] %c{2} (%M:%L) :: %m%n
|
The server log4j.properties file is used in conjunction with the client Log4j property files. It specifies the logging level and how the server will output the messages. As with Log4j-enabled application, you can define multiple appenders. This is true, as well, for both the client service and service modules
Listing 2 describes a typical Log4j property file. The server side of the WebServiceAppender uses default Log4j Appenders. Potentially, the server side Appender could call another WebServiceAppender and chain the log information:
Listing 2. Server side Log4j property file
#set the level of the root logger
log4j.rootLogger = INFO, FILE
#set own logger
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.file=c:/temp/log4j/server/server.log
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%p [%t] %c{2} (%M:%L) :: %m%n
|
The sample client program is a Plain Old Java Object (POJO) that logs a message and is configured to use the web service appender to handle its messages. Listing 3 shows an example of this.
Listing 3. Sample of a client application using WebServiceAppender
package com.carmelouria.logging.test;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
/**
* @author Carmelo Uria
*
*/
public class LoggingSample
{
private static Logger logger = Logger.getLogger(LoggingSample.class.getName());
/**
*
*/
public LoggingSample()
{
super();
PropertyConfigurator.configure("c:/temp/log4j.properties");
logger.log(Level.INFO, "LoggingSample instantiation...");
System.out.println("finished...");
}
public static void main(String[] args)
{
LoggingSample sample = new LoggingSample();
}
}
|
The WebServiceAppender is required so that it can send the messages to the given web service. WebServiceAppender inherits org.log4j.Appender, which allows the use of log4.properties and becomes a valid Log4j Appender.
It uses the Java API for XML-based Remote Procedure Call (JAX-RPC) to send the message to the service. JAX-RPC is a specification that describes application programming interfaces (APIs) and conventions for building web services and web services clients that use RPCs and XML. JAX-RPC is also known as JSR 101.
The LoggingEvent is dissected and represented as XML through a SOAPElement. The javax.xml.soap.SOAPElement interface means that the Service Endpoint Interface will contain a parameter or return a value of type javax.xml.soap.SOAPElement for each place in the schema where <xsd:any/> is used. It basically is a wrapper around the XML parameters without having a corresponding serializer/deserializer Java class. For example, once the client requests to log a message, a LoggEvent object is created and passed to an Appender. In this case, the Appender is the WebServiceAppender. The Appender retrieves the event and dissects the information within the event. Additional information is added, like the hostname, so that you know which system the message came from. The append method also converts the information into a SOAPElement that gets passed to the web service through the executeWebService method. Using the SOAPElement allows for the extensibility of a future version of the WebServiceAppender.
Listing 4. Append method that executes the WebServiceAppender service
protected void append(LoggingEvent event)
{
// create Web Service client using endpoint
if (endpoint == null)
{
System.out.println("no endpoint set. Check configuration file");
System.out.println("[" + hostname + "] " + this.layout.format(event));
return;
}
executeWebService(event);
}
private void executeWebService(LoggingEvent event)
{
SoapClient client = new SoapClient();
URL endPoint = null;
try
{
endPoint = new URL(getendpoint());
}
catch (MalformedURLException e1)
{
e1.printStackTrace();
}
String nameSpace = "http://ejb.logging.carmelouria.com";
QName serviceName = new QName(nameSpace, "LogAppenderServiceService");
QName operation = new QName(nameSpace, "log");
QName port = new QName(nameSpace, "LogAppenderService");
Parameter message =
new Parameter("log", Constants.XSD_ANY, SOAPElement.class, ParameterMode.IN);
try
{
/**
*create SOAPElement from LoggingEvent need hostname
*/
Level level = event.getLevel();
String sysLog = "<syslog>" + new Integer(level.getSyslogEquivalent()).toString()
+ "</syslog>";
String startTime = new Long(LoggingEvent.getStartTime()).toString();
String timeTag = "<start_time>" + startTime + "</start_time>";
String hostName = "<hostname>" + InetAddress.getLocalHost() +
"</hostname>";
String threadName = "<thread_name>" + event.getThreadName()
+"</thread_name>";
String logger = "<logger>" + event.getLoggerName() + "</logger>";
String eventMessage = "<message>" + event.getRenderedMessage() +
"</message>";
String log = hostName + threadName + logger + timeTag + sysLog +
eventMessage;
String throwableInformation[] = event.getThrowableStrRep();
if (throwableInformation != null)
{
for (int i = 0; i < throwableInformation.length; i++)
{
String throwable = "<throwable_information>" + throwableInformation[i] +
"</throwable_information>";
log += throwable;
}
}
String ndcString = event.getNDC();
if (throwableInformation != null)
{
String throwable = <ndc>" + ndcString + </ndc>";
log += throwable;
}
message.setValue(SOAPElementFactory.create(<log>" + log + </log>"));
}
catch (UnknownHostException unknownHostException)
{
unknownHostException.printStackTrace();
}
catch (SOAPException e2)
{
e2.printStackTrace();
}
Parameter resultType = newParameter("logResponse",
Constants.WEBSERVICES_VOID,
Object.class,
ParameterMode.OUT);
Parameter[] parameters = { message };
try
{
// execute client
Object result =
client.execute(endPoint, serviceName, operation, "wrapped", null,
port, resultType, parameters);
if ((result != null) && (result instanceof String))
System.out.println((String) result);
}
catch (ClientException e)
{
e.printStackTrace();
}
}
|
Unfortunately,
Log4j's LoggingEvent does not include the hostname. One of
the requirements for the web service appender is that a hostname is required. Before creating the SOAPElement, you can add the hostname into the XML as follows:
String hostName = "<hostname>" + InetAddress.getLocalHost() + "</hostname>";
SoapElementFactory is a class that basically wraps the creation of SOAPElement. It supports the creation of both the IBM and the Java SOAPElement implementations, as Listing 5 shows.
Listing 5. Create method from the SoapElementFactory class
public static javax.xml.soap.SOAPElement create(String xml) throws SOAPException
{
com.ibm.ws.webservices.engine.xmlsoap.SOAPFactory factory =
(com.ibm.ws.webservices.engine.xmlsoap.SOAPFactory)
com.ibm.ws.webservices.engine.xmlsoap.SOAPFactory
.newInstance();
SOAPElement element =
(javax.xml.soap.SOAPElement)factory.createElementFromXMLString(xml);
return(element);
}
public static SOAPElement create(String arg0, String arg1, String arg2,
boolean ibmSoapElement) throws
SOAPException
{
if (ibmSoapElement)
{
SOAPFactory soapFactory =
(com.ibm.ws.webservices.engine.xmlsoap.SOAPFactory)
com.ibm.ws.webservices.engine.xmlsoap.SOAPFactory.newInstance();
return (soapFactory.createSOAPElement(arg0, arg1));
}
javax.xml.soap.SOAPFactory soapFactory =
javax.xml.soap.SOAPFactory.newInstance();
return (soapFactory.createElement(arg0, arg1, arg2));
}
|
SoapClient is a class that wraps the JAX-RPC implementation of its Call interface. The javax.xml.rpc.Call interface provides support for the dynamic invocation of a service endpoint. The javax.xml.rpc.Service interface acts as a factory for the creation of Call instances.
Listing 6 shows how the client calls the service dynamically. This potentially allows the service to change without generating the client proxy to access the remote service.
Listing 6. The call method from the SoapClient class
private Object call(SoapService service, QName operation, QName portType,
String operationStyleProperty,
String encodingURIProperty, Parameter returnType,
Parameter[] parameters) throws ClientException
{
QName portName;
String response = null;
Object results = null;
Call call = null;
try
{
// check to see if Service object exists
if (service == null)
throw new ClientException("Invalid Service object. It maybe null.");
// retrieve call from Service object
call = service.createCall();
call.setOperationName(operation);
call.setPortTypeName(portType);
// check call object
if (call == null)
throw new ClientException("invalid operation. Call object is null.");
// set default values
if (operationStyleProperty == null)
call.setProperty(Call.OPERATION_STYLE_PROPERTY,
OPERATION_STYLE_DOCUMENT_TYPE);
else
call.setProperty(Call.OPERATION_STYLE_PROPERTY,
operationStyleProperty);
if (encodingURIProperty == null)
call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY,
ENCODING_LITERAL);
else
call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY,
encodingURIProperty);
call.setTargetEndpointAddress(service.getServiceEndPoint());
//create Parameter class for SoapClient
for (int i = 0; i < parameters.length; i++)
{
Class classObject = parameters[i].getClassObject();
if (classObject != null)
call.addParameter(parameters[i].getName(), parameters[i].getXmlType(),
parameters[i].getClassObject(), parameters[i].getMode());
else
call.addParameter(parameters[i].getName(), parameters[i].getXmlType(),
parameters[i].getMode());
}
// pass parameter as ReturnType
if (returnType != null)
{
if (returnType.getClassObject() != null)
call.setReturnType(returnType.getXmlType(), returnType.getClassObject());
else
call.setReturnType(returnType.getXmlType());
}
Object[] request = new Object[parameters.length];
// add parameter values
for (int i = 0; i < request.length; i++)
{
request[i] = parameters[i].getValue();
}
results = call.invoke(request);
}
catch (SOAPFaultException e)
{
System.out.println(e.getFaultString());
e.getStackTrace();
throw new ClientException(e.getLocalizedMessage(), e);
}
catch (ServiceException serviceException)
{
serviceException.getStackTrace();
throw new ClientException(serviceException.getLocalizedMessage(),
serviceException);
}
catch (RemoteException exception)
{
exception.printStackTrace();
throw new ClientException(exception.getLocalizedMessage(), exception);
}
return (results); }
|
The Log4j.server.properties file contains a basic Log4j configuration file that lets you specify what logs will be posted on the web services system.
Listing 7. Log4j.server.properties file
#set the level of the root logger
log4j.rootLogger = INFO, FILE
#set own logger
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.file=c:/temp/log4j/server/server.log
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%p [%t] %c{2} (%M:%L) :: %m%n
|
LogAppenderBean.java is the EJB that the
web service appender service uses. The service initiates LogAppenderBean to process each request from each web service appender client.
Listing 8 shows the log from the WebServiceAppender EJB. This method parses the message from the clients and logs the client information back on the server side of the service.
Listing 8. The log method from LogAppenderBean
public void log(SOAPElement message)
{
try
{
InputSource source = ((IBMSOAPElement)
message).toInputSource(false);
Document document = Parser.parse(source);
String log = null;
String hostname =
document.selectSingleNode("//hostname").getText();
String threadName =
document.selectSingleNode("//thread_name").getText();
String syslog =
document.selectSingleNode("//syslog").getText();
String startTime = new Long(
document.selectSingleNode("//start_time").
getText()).toString();
log = '[' + startTime + ':' + hostname + ':' + threadName +
"] " + document.selectSingleNode(
"//message").getText();
// retrieve any throwable messages
List throwableList = document.selectNodes(
"//throwable_information");
if(throwableList != null)
{
Iterator throwables = throwableList.iterator();
while(throwables.hasNext())
{
log += '\n' + ((Node)throwables.next()).getText();
}
log += '\n';
}
logger.log(Level.toLevel(new Integer(syslog).intValue()),
log);
logger.log(Level.INFO,log);
}
catch(ParserException parseException)
{
parseException.printStackTrace();
}
catch (SAXException e)
{
e.printStackTrace();
}
}
|
The contents of each SOAPElement are retrieved through the InputSource of the IBM SOAPElement. Currently, only IBM WebSphere® Application Server (Application Server) supports this code (see Resources). However, if you remove the IBM SOAPElement, you can use the code with any application server. The IBM SOAPElement incorporates performance enhancements that are available in Application Server.
Each SOAPElement is read, parsed, and transformed with Dom4j. Dom4j is an object model representing an XML Tree in memory. Dom4j offers a easy-to-use API that provides a powerful set of features to process, manipulate, or navigate XML, work with XPath and XSLT, and integrate with SAX, JAXP, and DOM.
Any XML parser will work, however DOM4J allows the use of any SAX parser, for performance, as well as any standard XSLT transformers. Transformation is used to extract the elements from the client LoggingEvent that was sent to the Web Service Appender.
If you allow for the SOAPElement, you maintain the greatest amount of flexibility in your code. The Web Service Appender service could be modified to support any XML that sends to the service.
The following sample exhibits the possible output that can come from the Web Service Appender:
INFO [WebContainer : 0] ejb.LogAppenderBean (log:?) :: [1111513482641:OO7-64BIT/9.48.114.183:main]LoggingSample instantiation...
OO7-64BIT/9.48.114.183 is the machine name and IP address and main is the method name from where the log is coming.
The Web Service Appender is an essential tool for centralizing logs in one location. Since the Web Service Appender is a subset of Log4j's Appender class, configuring and using the Appender is straightforward. You can modify the Log4j properties file so that existing applications and services that use Log4j can quickly use the Web Service Appender.
| Description | Name | Size | Download method |
|---|---|---|---|
| Foundation Class Library | foundation.zip | 47 KB | HTTP |
| Logging Web Service J2EE Application | LoggingWebService.ear | 1976 KB | HTTP |
| Unit Test Sample Code | SoapClientTest.java | 5 KB | HTTP |
Information about download methods
- Learn more about Log4j on the Apache web site.
- Read, write, navigate, create, and modify XML documents with Dom4j.
- Find out more about the SOAPElement Interface and the Call interface.
-
Get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®. You can download evaluation versions of the products at no charge, or select the Linux® or Windows® version of developerWorks' Software Evaluation Kit.
- Browse for books on these and other technical topics.
- Get involved in the developerWorks community by participating in
developerWorks blogs.
- The IBM developerWorks team hosts hundreds of technical briefings around the world which you can attend at no charge.
- Want more? The developerWorks SOA and web services zone hosts hundreds of informative articles and introductory, intermediate, and advanced tutorials on how to develop web services applications.