Creating the LogService

In order to make a logging package useful it needs to be easy and convenient for developers to configure and use. This can be accomplished by providing a logging service within a server-side application framework that will abstract out a lot of the configuration and setup issues related to providing a logging service.

For our purposes here I have chosen to use and extend a lightweight, vendor-independent framework for abstracting out server-side development problems.

The system used here adds to an implementation authored by Humphrey Sheil in an article published in the September 2000 issue of JavaWorld. The relevant article can be found at http://www.javaworld.com/javaworld/jw-09-2000/jw-0929-ejbframe.html.

Its purpose is to locate and initialize basic service components that exist as tools for enterprise application developers. Some examples of services provided by the basic implementation are: JNDI lookup, database connection manager, configuration service, and of course a logging service. Each service uses a specific configuration file for initialization. LogService will use a configuration file called LogService.properties.

The logging service within the original implementation is a very simple and non-distributed implementation that we will replace with the JLog package. We will also modify the log service component so that it can handle the added complexity of configuring and managing distributed application logs and the various logging options that we want to implement.

click to expand

The above diagram gives a high level overview of how the framework fits into the overall picture of an application server and the service it provides to Java enterprise software components. It's important to understand in this relationship that the framework services are available to all applications running within the application server's Java virtual machine, and the "framework manager" is responsible for initializing these services. It does this by "discovering" the services through the use of the Java Reflection API.

Note 

For more information regarding the specifics of how the core framework is designed please reference Humphrey Sheil's Java World article.

The following class diagram provides an overall view of the relationships that exist within the framework:

click to expand

As you can see from the above diagram, each service class extends a base FrameworkComponent class that the FrameworkManager is responsible for initializing. The FrameworkManager class uses the Singleton pattern (see below) in that there is only ever one instance of it.

Note 

From "Design Patterns" by Gamma, et al., 1995: "Singletons ensure a class only has one instance, and provide a global point of access to it."

The only class left in the diagram is the WLStartUp class. This is a WebLogic Startup class that the application server can call at startup that will initialize the entire framework.

For the rest of the chapter our main focus will be on the LogService component of this framework, although we will use the JNDIService component to accomplish some of what we need to do in building a configurable LogService.

The Requirements

The first thing we should do to is to determine what our LogService must do. Our previous JMS logging example will help us to determine some of what our new functionality should be. In our example there were many tasks that the example had to undertake before it could begin any actual logging. Examples of these tasks include:

  • Use of JNDI to lookup objects

  • Initialization of JMS objects

  • Specification of a logging topic

  • ConfiguringJLog component output media (file, console)

  • ConfiguringJLog output formats (XML, HTML, LONG)

None of the above tasks need to be performed by all applications that wish to utilize logging. So the above tasks are abstracted into services. All JNDI services (object lookups, object binding) are contained within the JNDIService component. Initialization of JMS objects, specification of a logging topic, and configuringJLog components are all handled by the LogService component and its supporting classes. This goes back to our earlier discussion of the Façade pattern. Each service within this framework is an implementation of that pattern.

After some analysis we determine that our LogService class should have the following responsibilities:

  • Retrieve configuration information for itself and for log instances

  • Initialize JMS components for use withJLog's JMSLog components

  • Create JLog components (readers, writers, filters) via factory classes

  • Provide interface methods for application logging

  • Allow modification of the service properties dynamically

Looking at the first item on our list, we need a way to retrieve configuration information for our LogService as well as for the different log instances that the LogService will create for applications to use for logging messages.

LogService Configuration

For each service that exists within the application framework there is a properties file used to hold configuration information for each respective service. For the LogService there are two properties within its logservice.properties file:

  • debug

    The global DEBUG flag for the LogService. This provides its initial value upon startup. It can be toggled through JMS as we'll see later.

  • logconfiglocation

    The location of the XML log configuration file. This file contains configuration information detailing what kind of application logs should be created by the LogService upon initialization. We will look more closely at this file in the next section.

Here is a sample configuration for logservice.properties:

     logconfiglocation=config\\mydomain\\logconfig\\logconfig.xml     debug=true 

XML Configuration File

We tell the LogService what types of application logs we'd like with an XML configuration file. Let's take a quick look at a sample XML configuration file and then we'll go over the options available to you when configuring a log instance:

     <?xml version="1.0"?>     <!DOCTYPE doctype SYSTEM "logconfig.dtd">     <LOGSERVICE>       <LOG name="testlog" usefilter="true">         <DISTRIBUTED datatype="object">            <READ>              <MEDIA>                 <CONSOLE format="XML"/>              </MEDIA>            </READ>            <WRITE/>         </DISTRIBUTED>       </LOG>     </LOGSERVICE> 

Let's step through each element in the above configuration:

  • <LOGSERVICE>

    Always the root element.

  • <LOG name="testlog" usefilter="true">

    The LOG element indicates a log to be created by LogService. We give it a name that applications will know it by (testlog), and indicate that we'd like this log to work as a filter on the Log.global log instance, meaning that all log messages sent to the global log with the title testlog will be filtered to this log instance.

  • <DISTRIBUTED datatype="object">

    This tag indicates that we wish this to be a distributed log, meaning that we'll use JMS for logging messages. We also indicate what the data type will be of the messages (object) since JMS supports different types. (This data type needs to match the data type of the corresponding distributed writer.)

  • <READ>

    This tag indicates that this LogService should set up a distributed log in read mode to read incoming messages from a JMS topic of the same name as this log instance.

  • <MEDIA>

    We must select a media type for the incoming messages to be sent to, along with a format. In this case we're logging in XML format to the console

  • <CONSOLE format="XML"/>

    Log output to be in XML.

  • <WRITE/>

    This tag indicates that this LogService should set up a distributed log in write mode to send outgoing messages to a JMS topic of the same name as this log instance.

What this log configuration says:

Note 

Create a filter on the global log for all messages entitled testlog. Make a distributed log reader (meaning that it subscribes to a JMS topic named after this log filter's name) that logs messages to the console in XML format. Also, create a writer that publishes messages to this log topic.

After all this is interpreted and performed by the LogService what we have is a distributed read/write log filter on the global log for messages entitled testlog. It is a bit unorthodox to have a distributed log in read/write mode because ideally in a distributed environment one machine would be writing messages to the log while another machine processes those messages. We have created a read/write distributed log just for the sake of demonstration.

Now let's take a look at all the tags that can be used to configure a LogService. The simplest way is through looking at the DTD for the XML configuration:

     <!ELEMENT LOGSERVICE (LOG+)> 

The <LOG> tag represents a single Log configuration, and uses the following attributes:

  • name

    Required. This option is used to indicate the name of the log instance.

  • usefilter

    true or false. This option is used to indicate whether this log instance is to be created as a separate log object or simply as a filter on the global log that will filter messages sent to the global log with a title matching this log's name. The major difference between the two configurations is that using a filter is simply indicating that we're going to use the global log to log messages and that messages logged to the global log with this log's name will be filtered and sent to a different log destination.

  • global(optional)

    true or false. This option is used to indicate that we wish to simply use the global log for this log instead of creating a new log instance or a filter.

     <!ELEMENT LOG  (DISTRIBUTED | NONDISTRIBUTED )>     <!ATTLIST LOG  name      CDATA     #REQUIRED                    usefilter CDATA     #IMPLIED                    global    CDATA     #IMPLIED                    a-dtype   NMTOKENS  'usefilter boolean                                         global    boolean' > 

The <DISTRIBUTED> tag indicates that this log instance will be a distributed log. Again this means that a log instance will be created that will make use of JMS either for reading messages from a topic or publishing messages to a topic based on the configuration options described below. When this tag is used one of three configuration options are available as far as READ/WRITE goes:

  • READ

    A log instance will be created that reads messages from a JMS topic

  • WRITE

    A log instance will be created that writes messages to a JMS topic

  • READ/WRITE

    A log instance will be created and both a reader and a writer will be available for this log instance.

     <!ELEMENT DISTRIBUTED (READ?, WRITE? )>     <!ATTLIST DISTRIBUTED       datatype (OBJECT | TEXT ) #REQUIRED     >     <!ELEMENT NONDISTRIBUTED (READ, WRITE )>     <!ELEMENT READ (MEDIA )>     <!ELEMENT WRITE EMPTY> 

The following tags are used in conjunction with the <MEDIA> tag. All of the following tags also must have the following mandatory attribute:

     format (XML, LONG, MEDIUM, SHORT, HTMLLONG, HTMLTABLE) #REQUIRED 

This option is used to indicate what format all messages will be logged in:

     <!ELEMENT MEDIA (FILE | CONSOLE)>     <!ELEMENT DATABASE EMPTY>     <!ATTLIST DATABASE table CDATA #REQUIRED       format (XML | LONG | SHORT | MEDIUM | HTMLLONG | HTMLTABLE ) #REQUIRED     >     <!ELEMENT FILE EMPTY>     <!ATTLIST FILE       usedate (true | false) #REQUIRED       path CDATA #REQUIRED       format (XML | LONG | SHORT | MEDIUM | HTMLLONG | HTMLTABLE) #REQUIRED     >     <!ELEMENT CONSOLE EMPTY>     <!ATTLIST CONSOLE       format (XML | LONG | SHORT | MEDIUM | HTMLLONG | HTMLTABLE ) #REQUIRED     >     <!ELEMENT NAME EMPTY>     <!ENTITY % true " "> 

Initializing LogService

Now that we've talked about how to set up log instances within the LogService's XML configuration file, the next item on our list is the initialization of the JLog components performed during LogService's initialization. Upon initialization, the FrameworkManager will call LogService's constructor. The LogService will perform a few tasks within its constructor:

  • Call the parent FrameworkComponent constructor which will load the logservice.properties file for this service.

  • Retrieve references to three different JLog factory objects which we'll talk more about later.

  • Retrieve the initial value for the DEBUG setting for the LogService, a global toggle switch that can be activated through JMS.

  • Retrieve the name and location of the logconfig.xml file containing the descriptions of log instances to be created, which we talked about earlier.

  • Process each log entry contained within the XML configuration file and initialize JLog components accordingly.

  • Set up the LogService's dynamic configuration ability, which will allow run-time configuration of the service itself. We'll talk more about this dynamic configuration later.

Now let's look at the code for the constructor of the framework.logging.LogService component:

       public LogService(String inConfigFileName) throws InitializeException {          // superclass is framework.manager.FrameworkComponent          super(inConfigFileName); 

Initialize each of the factory objects used by this service to create various log components:

          logEventProducerFactory = LogEventProducerFactory.getInstance();          logEventListenerFactory = LogEventListenerFactory.getlnstance();          logFilterFactory = LogFilterFactory.getInstance(); 

Set the global debug initial value:

          String debug = serviceProperties.getProperty(debugProperty);          DEBUG = Boolean.valueOf(debug).booleanValue(); 

Get the XML log configuration file location:

          String logConfigFile =             serviceProperties.getProperty(logConfigLocationProperty); 

Parse the XML configuration file and receive array of LogProperties, one for each <LOG> object:

          LogProperties logConfigs[] = parseXmlConfig(logConfigFile); 

Initialize JMS for distributed logs:

          initJMS(); 

Process all log configuration entries and create log instances:

          for(int i = 0; i < logConfigs.length; i++) {             BootLogger.logDebug (logConfigs[i].toString());             if (logConfigs[i].isDistributedLog()) {                setupJMSLog(logConfigs[i]);             }             else {                setupLocalLog(logConfigs[i]);             }             BootLogger.logDebug("Log successfully created: " +               logConfigs[i].getLogName());             } 

This LogServiceConfig service is a message-driven mechanism for making changes to the service while it's running, which we'll look at towards the end of the chapter:

               setupLogServiceConfig(logConfigTopic);               BootLogger.logDebug("Initialized LogService Config subscriber");           }       } 

Looking at the code above there's a couple of things that need to be discussed. First let's talk about the three "factory" objects that are retrieved. As we talked about earlier, the JLog package contains two major interface types: event producers and event listeners. The factory objects LogEventProducerFactory and LogEventListenerFactory are used by the LogService to create objects that implement these interfaces. The third factory is the LogFilterFactory that creates objects that implement the ILogFilter interface. If you recall our earlier diagram, the ILogFilter interface was special in that it extended both the event listener and event producer interfaces, thus we need a factory to create this type of component.

The second item to talk about is the LogProperties class. When we parse the log XML configuration file, each Log entry in the file is represented by a LogProperties object. So in the LogService constructor a method is called to parse the XML configuration and an array of LogProperties objects representing each log entry in the configuration file is returned. These LogProperties objects are then used in the creation of log components.

The following class diagram shows the LogService class with its supporting classes. Additionally, the JLog interfaces that are created by the supporting factory classes are shown to help tie together the relationship between LogService and the JLog package:

click to expand

Looking at the diagram above, we can see the LogService class that we've been talking about, along with the other classes also that are used to help the LogService initialize the various JLog components. As we stated earlier, the LogService class contains references to three factory objects. The LogEventProducerFactory creates objects that implement the ILogEventProducer interface. Examples of classes implementing the ILogEventProducer interface are: log readers, logs, and log filters. However, log filters are a bit different, and have a factory dedicated to creating instances of them.

The LogEventListenerFactory creates objects that implement the ILogEventListener interface. Currently LogWriter objects are the only type of class created by this factory.

The final factory class is the LogFilterFactory, which is used for creating LogFilter objects implementing the ILogFilter interface. Filter objects are special because they implement both the ILogEventProducer and ILogEventListener interfaces, and are to be thought of more as conduit connecting various other logging components. For example a filter is used to capture log messages meeting specific criteria being sent to a LogEventProducer and in turn sending them to a LogEventListener desiring those filtered messages.

The final supporting class is the LogProperties class. As we stated earlier, it is used to hold the various options available for the creation and configuration of a log instance. The LogService class creates and populates instances of LogProperties objects on startup. For each entry in the LogService XML log configuration, a LogProperties object is created and populated with that log's configuration information. Once created, a LogProperties object is used in the creation of the various logging components performed by the various factories.

LogService API

Now that we've covered the configuration and initialization of the LogService it's time to discuss what interface methods the LogService provides for application logging. This is easily accomplished by either inspecting the source code, or browsing the JavaDoc documentation for the framework package. First we need to generate the JavaDocs. To build the JavaDoc documentation for the framework return to a command window and the chapter directory. To build the JavaDoc documentation, we execute the command ant docs for the following target:

     <target name="docs">       <mkdir dir="${docs}" />       <javadoc packagenames="framework.*"          sourcepath="src"          destdir="${docs}"          author="true"          version="true"          use="true"          windowtitle="Enterprise Java Framework"       />     </target> 

The documentation is generated and placed into a directory called docs relative to our base directory.

Open the index file and click on the link for the LogService class to view the methods that are available for this class.

The important methods to note here as far as an application programmer goes are the various logXXX() methods that are available. There are four different levels of severity:

  • Trace

  • Debug

  • Warning

  • Error

The other methods provided won't be of much interest to most application programmers but are useful concerning types of log available in the LogService. There is also the ability to directly retrieve a Log instance and use it, but this would tie anyone using the Log instance directly to the JLog package, rather than to our LogService façade, so it's not recommended.

Servlet Example

Now that we've covered what the LogService has to offer in the way of functionality let's go through an example of how an Enterprise Java component, in this case a servlet, can use the service.

Let's take a look at the code for the servlet:

     package examples.frameworkServlet;     import javax.servlet.http.HttpServlet;     import javax.servlet.http.HttpServletRequest;     import javax.servlet.http.HttpServletResponse;     import java.io.PrintWriter;     import java.io.IOException;     import framework.logging.LogService;     public class LoggingServletExample extends HttpServlet {        int count = 0;        public void doGet (HttpServletRequest request,           HttpServletResponse response) {           LogService.logDebug("examples.frameworkServlet",              "Entered doGet()"); 

The number of times this servlet is accessed is incremented at each invocation:

           count++;           LogService.log("examples.frameworkServlet",              "examplelogservlet accessed " + count + "times.");           try {              PrintWriter out = response.getWriter();              out.println("examplelogservlet accessed " + count +                " times.");              out.flush();              out.close();           }           catch(IOException e) {              LogService.logError("examples.frameworkServlet",                "Error getting output writer: " + e.getMessage());           }           LogService.logDebug("examples.frameworkServlet",             "Exited doGet()");        }     } 

The code for the servlet is very simple and is meant as a simple demonstration of how to use the LogService within a Java application container. You can see that each call to the LogService logXXX() methods pass two parameters; the first is the title of the message, the second the message to be logged. The key thing to note is the title of the message. The title is what's being used to filter messages to the logs for this example. Let's take a look at the XML configuration entries for this example:

     <LOG name="examples.frameworkServlet" usefilter="true">       <NONDISTRIBUTED>          <READ>            <MEDIA>               <FILE path="c:\" usedate="false" format="LONG"/>            </MEDIA>          </READ>          <WRITE/>       </NONDISTRIBUTED>     </LOG> 

Even though the following two entries essentially make this log a distributed read/write on the same machine which could be accomplished within a single Log entry, the next two entries demonstrate how one Log instance could be set up as a distributed read log on one machine while another machine could setup a distributed write log:

     <LOG name="examples.frameworkServlet" usefilter="true">       <DISTRIBUTED datatype="OBJECT">          <READ>             <MEDIA>                <FILE path="C:\" usedate="false" format="XML"/>             </MEDIA>          </READ>       </DISTRIBUTED>     </LOG> 

We could imagine that this entry is in logconfig.xml on another machine:

     <LOG name="examples.frameworkServlet" usefilter="true">       <DISTRIBUTED datatype="OBJECT">          <WRITE/>       </DISTRIBUTED>     </LOG> 

Recall that examples.frameworkServlet is also configured as the name of the JMS topic used as the destination for logging messages in this distributed version.

If you chose the latter logconfig.xml entries, then the service messages would be handled by JMS. There are a few steps that need to be taken before running the servlet example. We need to install the servlet and the service framework within our application server. For this example we've used the WebLogic 6.0 server, so the setup will be somewhat specific to that application server but with some modifications to the framework, this example could easily be run with any J2EE-compliant application server. Slight differences in deployment settings and classpaths are easily handled by editing the build.xml script.

We first need to tell the server how to install the servlet with the following web.xml file:

     <?xml version="1.0" encoding="ISO-8859-1"?>     <!DOCTYPE web-app         PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"         "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">     <web-app>        <servlet>           <servlet-name>              examplelogservlet           </servlet-name>           <servlet-class>              examples.frameworkServlet.LoggingServletExample           </servlet-class>        </servlet>        <servlet-mapping>           <servlet-name>              examplelogservlet           </servlet-name>           <url-pattern>              /examplelogservlet           </url-pattern>        </servlet-mapping>     </web-app> 

This minimal file tells the server to install our servlet and to make it visible at the address examplelogservlet relative to our application URL. We store this file in the same directory as the source code for the servlet. Later it will be bundled into the WAR file and deployed to the server.

The deployment targets for the servlet example are as follows. First we compile the servlet code into a build directory (refer to our original settings for these values):

       <target name="servletmake" depends="framework">         <javac           srcdir="${framework_servlet_src}"           destdir="${framework_servlet_build}"           classpath="${framework_servlet_classpath}"         />       </target> 

Next we build the WAR file logservlet.war for the servlet using the class file from our build directory. This is a very simple structure, because it merely contains the servlet class and the basic web.xml file above:

       <target name="servletwar" depends="servletmake">         <war warfile="logservlet.war" webxml="${framework_servlet_src}/web.xml">           <classes dir="${framework_servlet_build}" />         </war>       </target> 

The next step is to make a fresh copy of the service framework in the server installation. This target copies the compiled classes to a new directory under the WebLogic installation. These classes are suitable for including in the run-time classpath of the WebLogic instance, rather than as an specific application library, since we may want them to be available to several client applications:

       <target name="builddeploy" depends="framework">         <copy todir="${serverclasses}/framework">            <fileset dir="${framework_build}/framework"/>         </copy>       </target> 

The next step is to copy the WAR file to the application deployment directory. In this case, the directory resolves to C:\bea\wlserver6.0\config\mydomain\applications:

       <target name="servletexample" depends="servletwar, builddeploy">         <move file="./logservlet.war" todir="${apphome}"/>       </target> 

If you go back to the frameworkprepare target of our first simple example, you will see that we have already copied over the relevant configuration files from the config folder of the home directory to the logconfig directory below our application server deployment root. These configuration files consist of:

  • logservice.properties

    Provides the bootstrap information to access the next file

  • logconfig.xml

    The configuration file for setting up logging services

  • logconfig.dtd

    The descriptor file for the XML above, which needs to be available when parsing the configuration file

In addition, there are two further configuration files used by other services within the framework:

  • jndiservice.properties

    Used with the system to connect to the messaging provider:

             framework.weblogic.jndi.url=t3://localhost:7001         framework.weblogic.jndi.factory=weblogic.jndi.T3InitialContextFactory 

  • dbconnectionservice.properties

    Connection details to test the database service (used in the EJB example later)

             dbconnection.properties         framework.db.user=scott         framework.db.password=tiger         framework.db.driver=oracle.jdbc.driver.OracleDriver         framework.db.url=jdbc:oracle:thin:@dbserver:1521:database         framework.db.executesanitycheck=false 

Concerning the database service, this is not essential to us. Providing that you have a suitable database driver in the classpath, and have configured the lookup URL, username, and password as above, then any database will do. The last property is set to false which means that no database access check will be attempted when the service is booting up, and so no corresponding errors will occur if you have not configured this service.

All these files are now located at C:\bea\wlserver6.0\config\mydomain\logconfig. The remaining task is to edit the WebLogic startup script (in mydomain\startWebLogic.cmd) to provide the appropriate system properties for the service, and to set the classpath.

Check that these directories and JAR files (including ".") are in the classpath in this order:

     C:\jaxp-1.1ea2\jaxp.jar     .\lib\weblogic_sp.jar     .\lib\weblogic.jar     .\config\mydomain\serverclasses     C:\jlog\jlog_1_01.jar     C:\jaxp-1.1ea2\crimson.jar     C:\ant\lib\ant.jar     C:\Oracle\Ora81\jdbc\classes111.zip 

The last library should contain the JDBC driver for your database, which we configured in dbconnectionservice.properties. Check that the following system properties are set in the main Java command to start the server. They tell the system where the framework code is, where the boot file for the LogService configuration details are, whether to set DEBUG on bootup, and whether to exit if there is a problem with the service:

     -Dframework.rootdir=C:\bea\wlserver6.0\config\mydomain\serverclasses     -Dframework.configdir=C:\bea\wlserver6.0\config\mydomain\logconfig     -Dframework.boot.debug=true     -Dframework.exitoniniterror=false     -Dweblogic.Domain=mydomain     -Dweblogic.Name=myserver     -Dbea.home=C:\bea     -Djava.security.policy==C:\bea\wlserver6.0/lib/weblogic.policy"     -Dweblogic.management.password=%WLS_PW% 

The very last thing to check is that the boot file logservice.properties file is pointing to the right configuration directory. This is a little tricky unless you notice that the WebLogic start up script jumps up two directories before executing the Java runtime. So the location of the configuration file as given in the boot file should be relative to this:

     logconfiglocation=config\\mydomain\\logconfig\\logconfig.xml     debug=true 

Note that this extra level of redirection is to accommodate the design of the underlying framework, which requires a similar kind of properties file for each service.

Running the Example

If you made any changes to the WebLogic configuration files, be sure to restart the server so that the changes will take effect. Once you've restarted the server we're ready to try to execute our example. Open up a browser window and enter the URL to point to the machine on which your WebLogic server is running with the path to the example servlet appended (http://localhost:7001/logservlet/examplelogservlet). If everything is working you should see a very simple output in the browser like this:

click to expand

Now let's take a look at the log files generated by running this example. The supplied logconfig.xml has two logs setup for this example, a distributed read/write, and a nondistributed log. One of them logs to a file in Long format and the other in XML format. The default setting shipped with the framework creates the files in directory C:\.

Examining the contents of the examples.frameworkServlet.xml log file, we see the following entries:

     <LogEntry>     <LogName>examples.frameworkServlet</LogName>     <EntryName></EntryName>     <Time>Thu Feb 15 23:26:20 GMT+00:00 2001</Time>     <Severity>2(Debug message)</Severity>     <Thread>jim1 (192.168.10.75)</Thread>     <Source>examples.frameworkServlet.LoggingServletExample.doGet(Logging ServletExample.     java:25)</Source>     <Title>examples.frameworkServlet</Title>     <Contents>Entered doGet()</Contents>     </LogEntry>     <LogEntry>     <LogName>examples.frameworkServlet</LogName>     <EntryName></EntryName>     <Time>Thu Feb 15 23:26:21 GMT+00:00 2001</Time>     <Severity>1(Trace message)</Severity>     <Thread>jim1 (192.168.10.75)</Thread>     <Source>examples.frameworkServlet.LoggingServletExample.doGet(Logging ServletExample.     Java:29)</Source>     <Title>examples.frameworkServlet</Title>     <Contents>examplelogservlet accessed 1 times.</Contents>     </LogEntry>     <LogEntry>     <LogName>examples.frameworkServlet</LogName>     <EntryName></EntryName>     <Time>Thu Feb 15 23:26:21 GMT+00:00 2001</Time>     <Severity>2 (Debug message)</Severity>     <Thread>jim1 (192.168.10.75)</Thread>     <Source>examples.frameworkServlet.LoggingServletExample.doGet (LoggingServletExampl1e.     java:43)</Source>     <Title>examples.frameworkServlet</Title>     <Contents>Exited doGet()</Contents>     </LogEntry> 

If you chose a DISTRIBUTED log for the servlet in the logconfig.xml file, then you can check the throughput of messages in the WebLogic console. Navigate to the Destinations folder under your JMS Server and select Monitor all Active JMS Destinations and you'll see the screen below. You need to have set up the examples.frameworkServlet topic that we mentioned earlier:

click to expand

EJB Example

Servlets aren't the only Java enterprise component capable of using the LogService. We'll now look at a simple EJB example that demonstrates how to use the LogService, as well as some of the other services that exist within the framework.

This is the entry in the logconfig.xml for this service:

       <LOG name="examples.frameworkEJB" usefilter="true">           <NONDISTRIBUTED>              <READ>                <MEDIA>                   <FILE path="C:\" usedate="false" format="LONG"/>                </MEDIA>              </READ>              <WRITE/>           </NONDISTRIBUTED>       </LOG> 

Let's take a look at the code for the FrameworkTesterBean:

     package examples.frameworkejb.ejb;     import java.sql.Connection;     import java.sql.ResultSet;     import java.sql.SQLException;     import java.sql.Statement;     import javax.ejb.CreateException;     import javax.ejb.SessionBean     import javax.ejb.SessionContext;     import javax.naming.InitialContext;     import javax.naming.NamingException;     import framework.dbconnection.DbConnectionService;     import framework.logging.LogService;     import framework.jndi.JNDIService;     import framework.jndi.JNDIServiceLookupException;     Public class FrameworkTesterBean implements SessionBean {        private SessionContext context = null; 

EJB lifecycle methods:

        public void ejbActivate() {}        public void ejbRemove() {}        public void ejbPassivate() {}        public void setSessionContext (SessionContext inContext) {         this.context = inContext;        } 

The following tests the logging, JNDI lookup, and database connectivity within the host application server:

        public void ejbCreate() throws CreateException {           LogService.logDebug("examples.frameworkEJB",                    "FrameworkTesterBean::ejbCreate() ENTRY");           LogService.logDebug("examples.frameworkEJB",                    "FrameworkTesterBean::ejbCreate() EXIT"); 

This method tests the service availability of the framework from within the EJB:

        public void doWork() {          LogService.logDebug("examples.frameworkEJB",                    "FrameworkTesterBean::doWork ENTRY");          try {             FrameworkTesterHome home =                (FrameworkTesterHome)                    JNDIService.findHome FrameworkTesterHome.JNDI_NAME);            }            catch(JNDIServiceLookupException jse) {              LogService.logError("examples.frameworkEJB",                  "FrameworkTesterBean::ejbCreate(): " +jse);              jse.printStackTrace();            } 

To test the DBConnectionService functionality, we look up the database emp table:

         try {           Statement stmt =             DbConnectionService.getConnection().createStatement();           stmt.execute("select * from emp");           ResultSet rs = stmt.getResultSet();           while (rs.next()) {              LogService.logDebug("examples.frameworkEJB",              rs.getString("empno") + " - " +              rs.getString("ename") + " - " +              rs.getString("deptno"));           }         }         catch(SQLException se) {           LogService.logError("examples.frameworkEJB",              "FrameworkTesterBean::doWork(): " +se);           se.printStackTrace();         }         LogService.logDebug("examples.frameworkEJB",              "FrameworkTesterBean::doWork EXIT");       }    } 

As you can see the implementation is very similar to that of our example servlet as far as the LogService is concerned. The only difference between the two examples, in terms of LogService use, is the title and contents of the messages we're logging.

To run the following example you need to have successfully set up a database in the dbconnectionservice.properties file. We saw an example of this earlier where we assumed an Oracle service available on machine dbserver at port 1521, and we use the standard example schema scott/tiger, which has an employee table emp. If you have a different database available, edit the dbconnectionservice.properties file and ensure that the JDBC driver is available in the WebLogic classpath.

To run the EJB example, return to the command-line window at the home directory and execute the command ant runejbexample. The targets are executed in the following order. The ejbmake target compiles the source code for the EJB:

       <target name="ejbmake" depends="framework">         <javac           srcdir="${framework_ejb_src}"           destdir="${framework_ejb_build}"           classpath="${framework_ejb_classpath}"         />       </target> 

This depends on the service framework code, which is compiled first. Next, we use Ant's ejbjar facility for creating the deployment package. Ant has built-in capability for compiling EJBs for WebLogic (although it uses WebLogic's own EJB compiler):

       <target name="ejbdeploy" depends="ejbmake, builddeploy">         <ejbjar srcdir="${framework_ejb_build}" flatdestdir="true"            descriptordir="${descriptorhome}">           <weblogic              destdir="${apphome}"              classpath="${framework_ejb_classpath}"           />           <include name="**/*-ejb-jar.xml"/>           <exclude name="**/*weblogic*.xml"/>         </ejbjar>       </target> 

Note the destdir attribute of the weblogic tag. This sends the EJB JAR file straight to the server's deployment base where it replaces any previous version and is re-installed automatically by the server. To run the example, we provide command-line arguments to a client test harness FrameworkTesterMain, passing in the initial context factory and provider URL:

       <target name="runejbexample" depends="ejbdeploy">         <java            classname="examples.frameworkejb.FrameworkTesterMain"            fork="yes"            classpath="${framework_ejb_build};${framework_classpath}"         >           <arg value="${host}"/>           <arg value="${initctx}"/>         </java>       </target> 

Note the two arguments consisting of the JNDI parameters necessary to connect to the bean. The output shows the test harness looking up the EJB on WebLogic, and executing a test call:

click to expand

Recall that the output of the configuration is specified in the logconfig.xml file. Here we specified output to a file in C:\. Examining the C:\examples.frameworkEJB.log file we see a sequence of log outputs like the following. The thread first enters the ejbCreate() method of the bean as the container activates the bean instance:

     Log name: Global System Log     Entry name:     Time: Fri Feb 16 23:50:33 GMT+00:00 2001     Severity: 2 (Debug message)     Title: examples.frameworkEJB     Thread: jim1 (192.168.10.75)     Source: examples.frameworkejb.ejb.FrameworkTesterBean.ejbCreate        (FrameworkTesterBean.java:62)     Contents: FrameworkTesterBean::ejbCreate() ENTRY     Log name: Global System Log     Entry name:     Time: Fri Feb 16 23:50:33 GMT+00:00 2001     Severity: 2 (Debug message)     Title: examples.frameworkEJB     Thread: jim1 (192.168.10.75)     Source: examples.frameworkejb.ejb.FrameworkTesterBean.ejbCreate        (FrameworkTesterBean.java:64)     Contents: FrameworkTesterBean::ejbCreate() EXIT 

Then the test harness calls up the doWork() method through the remote handle:

     Log name: Global System Log     Entry name:     Time: Fri Feb 16 23:50:33 GMT+00:00 2001.     Severity: 2 (Debug message)     Title: examples.frameworkEJB     Thread: jim1 (192.168.10.75)     Source: examples.frameworkejb.ejb.FrameworkTesterBean.doWork        (FrameworkTesterBean.java:73)     Contents: FrameworkTesterBean::doWork ENTRY 

The database is then queried, and you will get a number of results like these:

     Log name: Global System Log     Entry name:     Time: Fri Feb 16 23:50:33 GMT+00:00 2001     Severity: 2 (Debug message)     Title: examples.frameworkEJB     Thread: jim1 (192.168.10.75     Source: examples.frameworkejb.ejb.FrameworkTesterBean.doWork        (FrameworkTesterBean.java:99)     Contents: 7369 - SMITH - 20     Log name: Global System Log     Entry name:     Time: Fri Feb 16 23:50:33 GMT+00:00 2001     Severity: 2 (Debug message)     Title: examples.frameworkEJB     Thread: jim1 (192.168.10.75)     Source: examples.frameworkejb.ejb.FrameworkTesterBean.doWork        (FrameworkTesterBean.java:99)     Contents: 7499 - ALLEN - 30 

Finally, the method exits:

     Log name: Global System Log     Entry name:     Time: Fri Feb 16 23:50:33 GMT+00:00 2001     Severity: 2 (Debug message)     Title: examples.frameworkEJB     Thread: jim1 (192.168.10.75)     Source: examples.frameworkejb.ejb.FrameworkTesterBean.doWork        (FrameworkTesterBean.java:110)     Contents: FrameworkTesterBean::doWork EXIT 

If you set up the examples.frameworkEJB topic in WebLogic, then you could run a distributed JMS version of the logging service as we did in the servlet case. Simply edit the logconfig.xml file to define a distributed <LOG> for this service.

So we have seen that once we have done some initial server configuration and installation of the LogService, we can quickly prototype new useful logging services for Java enterprise components.

Using JMS to Enhance the LogService

Now that we have a LogService that is capable of being configured to create application logs, we turn our focus to another problem commonly found in real-world software implementations. It is quite a common occurrence in a production environment that problems are discovered that for one reason or another are difficult or impossible to recreate in a QA or development environment. For this reason it would be convenient if one were able to be given insight into the workings of the production system in a similar fashion to a QA or development environment.

The way to accomplish this is by the use of appropriate debugging or trace statements placed strategically within the software. This is a very common practice when developing software. It is also very common that accompanying statements like these will be a global flag controlling whether or not these statements will be executed. For example:

     if (LogService.DEBUG) {        LogService.logDebug("debug statement");     } 

The variable DEBUG would be a debug flag set somewhere within a class, perhaps by a global DEBUG flag. There are a few different ways in which this flag could be switched within the code. First there is the simple approach, which is "hard-coding" the value to true or false. This method requires code compilation every time debug statements need to be turned on or off. This is not a viable option for software running in a production environment.

The second method is to pass a run-time parameter to the system upon startup, which sets this flag to true or false and remains in that state throughout the life of the application instance. This option is viable in environments where software can be started and stopped without compromising the integrity of an entire system.

The third method is the most advanced method offering the most control. Controlling the flag setting during run time via a configuration tool that can change the value while the software system is up and running without having to restart the entire system. This method is ideal for systems that need to run continuously where it's not convenient to stop the entire system to place it into "debug mode".

The use of JMS will allow us the luxury of dynamically configuring our log service, no matter how many servers we have the service deployed on. All that we need to do is to simply create a JMS topic that our log service will subscribe to and create a tool that can be used to publish configuration settings to this same topic. The following diagram illustrates the concept:

click to expand

The methods needed to configure the LogService class to accommodate this "dynamic configuration" are shown below. The setupLogServiceConfig() method (contained within the LogService class) is used to set this LogService as a subscriber to the LogServiceConfig JMS topic, which is used to dynamically configure the LogService class:

     private void setupLogServiceConfig(String topicName        throws InitializeException { 

Set up a subscriber to the LogServiceConfig topic to watch for DEBUG toggle messages:

        try {           Topic topic = getTopic(topicName);           TopicSubscriber topicSubscriber =             topicSession.createSubscriber(topic);           topicSubscriber.setMessageListener(this);           if(LogService.DEBUG)              LogService.logDebug("LogService", "successfully initialized " +                "LogServiceConfig for LogService");        }        catch(JMSException e) {           throw new InitializeException("JMS Error initializing Log " +             "Service config subscriber", e);        }     } 

The above method retrieves the JMS topic and subscribes to it with LogService class being set as the listener. The onMessage() method implements the MessageListener interface and is used to receive configuration messages for this LogService. This object subscribes to a JMS topic that is used to broadcast configuration messages for all LogService objects on all application servers in a cluster:

     public void onMessage(Message msg) {        try { 

Right now we're only implementing one property and we can embed that into the message itself:

         DEBUG = msg.getBooleanProperty("debug");         LogService.logDebug("LogService",            "LogService DEBUG set to: " + DEBUG);         if(msg instanceof ObjectMessage) { 

If we want to use a serialized object to hold configuration info, we implement this in here.

         else if (msg instanceof TextMessage) { 

If we want to use a text message in XML or some other format we implement this here.

             }         }         catch (JMSException jmse) {             LogService.logError("LogService", "Error processing " +             "LogServiceConfig message: " + jmse.getMessage());         }     } 

The above method implements the MessageListener interface to respond to published messages.

To test your LogService and its ability to be configured via JMS messaging, a small GUI tool is included with the framework code for the chapter. Again, we can use Ant to build and run the configurator:

      <target name="runconfigurator" depends="framework">        <java           classname="framework.logging.config.LogServiceConfigurator"           fork="yes"           classpath="${framework_build};${framework_classpath}"        >          <arg value="${host}"/>        </java>      </target> 

Since this is our last build.xml entry, we close the <project> tag:

     </project> 

Note the hostname argument passed to the program; this enables the tool to initialize JMS objects. Remember to add the LogServiceConfig topic to WebLogic if you haven't already done so. Start your WebLogic server. Once that is complete, you simply run the test tool by returning to the command window and executing the command ant runconfigurator.

After you've run the configuration tool, and it has connected to the WebLogic server, you will be presented a GUI interface like this one:

click to expand

You can now toggle the LogService debug mode by simply setting the checkbox and clicking the Send button. A log, logging to the console, should have already been set up in the logconfig.xml watching the LogServiceConfig log. You can see the message confirming the debug setting. However, note that the LogService is not actually initialized until the service framework has booted you'll need to run the EJB or servlet tests to wake it up. The following is sample output from toggling the checkbox and shows the LogService receiving the new configuration setting:

     Log name: Global System Log     Entry name:     Time: Fri Feb 16 03:45:49 GMT+00:00 2001     Severity: 2 (Debug message)     Title: LogService     Thread: jim1 (192.168.10.75)     Source: framework.logging.LogService.onMessage(LogService.java:356)     Contents: LogService DEBUG set to: false     Log name: Global System Log     Entry name     Time: Fri Feb 16 03:46:05 GMT+00:00 2001     Severity: 2 (Debug message)     Title: LogService     Thread: jim1 (192.168.10.75)     Source: framework.logging.LogService.onMessage(LogService.Java:356)     Contents: LogService DEBUG set to: true 

You are encouraged to investigate and develop the service framework and logging system using the download bundle. Because of restrictions of space, and the complexities involved in integrating all the systems, we have not been able to incorporate all features and possibilities of the logging service in this chapter, but we hope to have provided useful architectural and practical advice on how to set up a distributed logging system.



Professional JMS
Professional JMS
ISBN: 1861004931
EAN: 2147483647
Year: 2000
Pages: 154

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net