Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

Manage message logging with the web service appender for Log4j

Carmelo Uria (curia@us.ibm.com), Senior IT Specialist, IBM Corporation
Carmelo Uria has architected and developed many J2EE and .NET applications. He is a Senior IT Specialist for the IBM Business Partner Technical Enablement team. He has been working for IBM for the past 5 years.

Summary:  Send log information to a centralized location using a customized web service appender for Log4j. The Log4j Appender gives you the capability to debug and track any issues in your Service-Oriented Architecture (SOA) solution.

Date:  07 Jun 2005
Level:  Intermediate

Comments:  

Introduction

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.


A brief primer on Log4j

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.


Client components

Client Log4j.properties file

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

			


Server Log4j.properties file

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
			

Sample client program test

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();
    }
}


WebServiceAppender

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();
        }
    }

Hostname

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

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

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); }


Service components

Log4j.server.properties

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

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.


Output

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.


Conclusion

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.



Downloads

DescriptionNameSizeDownload method
Foundation Class Libraryfoundation.zip47 KBHTTP
Logging Web Service J2EE ApplicationLoggingWebService.ear1976 KBHTTP
Unit Test Sample CodeSoapClientTest.java5 KBHTTP

Information about download methods


Resources

  • 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.

About the author

Carmelo Uria has architected and developed many J2EE and .NET applications. He is a Senior IT Specialist for the IBM Business Partner Technical Enablement team. He has been working for IBM for the past 5 years.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in

If you don't have an IBM ID and password, register here.


Forgot your IBM ID?


Forgot your password?
Change your password


By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)


By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=SOA and web services
ArticleID=84654
ArticleTitle=Manage message logging with the web service appender for Log4j
publish-date=06072005
author1-email=curia@us.ibm.com
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).