Developing Adapters
Logging is an essential feature of an adapter component. Most adapters are used to integrate different applications; they do not interact with end users while data is being processed. Unlike a front-end component, when an adapter encounters an error or warning condition, it cannot stop processing and wait for an end-user to respond.
With the ADK, you can log adapter activity by implementing a logging framework. This framework gives you the ability to log internationalized and localized messages to multiple output destinations. It provides a range of configuration parameters you can use to tailor message category, priority, format, and destination.
This section contains information about the following subjects:
The ADK logging toolkit allows you to log internationalized messages to multiple output destinations. The logging toolkit leverages the work of the Apache Log4j open source project. This product includes software developed by the Apache Software Foundation (http://www.apache.org).
The logging toolkit is a framework that wraps the necessary Log4j classes to provide added functionality for J2EE-compliant adapters. It is provided in the logtoolkit.jar
file under WLI_HOME
/lib
. This JAR file depends on DOM, XERCES, and Log4j. The XERCES dependency is satisfied by the weblogic.jar
and xmlx.jar
files provided with WebLogic Server. The required version of Log4j, log4j.jar
, is provided in WL_HOME
/common/lib
.
The Log4j package is distributed under the Apache public license, a full-fledged open source license certified by the open source initiative. The latest Log4j version, including full-source code, class files, and documentation, can be found at the Apache Log4j Web site (http://www.apache.org).
Throughout this section, you will see references to and code excerpts from the logging configuration file. This file is an .xml
file that is identified by the adapter logical name, such as BEA_WLS_DBMS_ADK.xml
. It contains the base information for the four logging concepts discussed in Logging Concepts and can be modified for your specific adapter.
The ADK provides a basic logging configuration file, BEA_WLS_SAMPLE_ADK.xml
, in WLI_HOME
/adapters/sample/src
. To modify this file for your adapter, run GenerateAdapterTemplate
. This utility customizes the sample version of the logging configuration file with information pertinent to your new adapter and places the customized version in the new adapter's development environment. For more information about GenerateAdapterTemplate
, see Creating a Custom Development Environment.
Before using the logging toolkit provided with the ADK, you should understand a few key concepts of the logging framework. Logging has four main components:
These components work together to enable you to log messages according to message type and priority, and to control, at run time, how these messages are formatted and where they are reported.
Categories identify log messages according to criteria you define and are a central concept of the logging framework. In the ADK, a category is identified by its name, such as BEA_WLS_SAMPLE_ADK.DesignTime
.
Categories are hierarchically defined and any category can inherit properties from a parent category. The hierarchy is defined as follows:
For example, BEA_WLS_SAMPLE_ADK.DesignTime
is a descendant of BEA_WLS_SAMPLE_ADK
which, in turn, is a descendant of the root category, as shown in the following diagram.
ROOT CATEGORY
|
|->BEA_WLS_SAMPLE_ADK
|
|->BEA_WLA_SAMPLE.ADK.DesignTime
The root category resides at the top of the hierarchy; it cannot be deleted or retrieved by name.
When you create categories, you should name them according to components in the adapter to which they belong. For example, if an adapter has a design-time user interface component, the adapter might have a category with the following name: BEA_WLS_SAMPLE_ADK.DesignTime
.
Every message has a priority that indicates its importance. Message priority is determined by the ILogger interface method used to log the message. For example, if you call the debug method on an ILogger instance, a debug message is generated.
The logging toolkit supports five possible priorities for a given message. These priorities are listed, in descending order of importance, in Table 5-1.
The BEA_WLS_SAMPLE_ADK
category has priority WARN
because of the following child element:
<priority value='WARN' class='com.bea.logging.LogPriority'/>
The class for the priority must be com.bea.logging.LogPriority
.
You can assign a priority to a category. If a given category is not assigned a priority, it inherits one from its closest ancestor with an assigned priority; that is, the inherited priority for a given category is equal to the first non-null priority above the given category in the hierarchy.
A log message is sent to the log destination if its priority is higher than or equal to the priority of its category. Otherwise, the message is not written to the log. A category without an assigned priority inherits one from the hierarchy. To ensure that all categories can eventually inherit a priority, the root category always has an assigned priority. A log statement of priority p, in a category with inherited priority q, is enabled if p >= q. This rule is based on the assumption that priorities are ordered as follows: DEBUG
< INFO
< WARN
< ERROR
< AUDIT
.
The logging framework allows an adapter to log messages to multiple destinations by using an interface called an appender. Log4j provides appenders for:
In addition, the ADK logging toolkit provides an appender that you can invoke to send a log message to your WebLogic Server log.
A category may refer to multiple appenders. Each enabled logging request for a given category is forwarded to all the appenders in that category, as well as all the appenders higher in the hierarchy. In other words, appenders are inherited cumulatively from the category hierarchy.
For example, if a console appender is added to the root category, then all enabled logging requests are displayed, at a minimum, on the console. If, in addition, a file appender is added to category C, then enabled logging requests for C and C's children are printed in a file and displayed on the console. It is possible to override this default behavior (that is, to stop appender inheritance from being cumulative) by setting the additivity flag to false.
Note: If you also add the console appender directly to C, you get two messages—one from C and one from root—on the console. The root category always logs to the console.
Listing 5-1 shows an appender for the WebLogic Server log.
Listing 5-1 Sample Code Showing an Appender for the WebLogic Server Log
<!--
A WeblogicAppender sends log output to the Weblogic log. If running outside
of WebLogic, the appender writes messages to System.out
-->
<appender name="WebLogicAppender"
class="com.bea.logging.WeblogicAppender"/>
</appender>
Log4j enables you to customize the format of a log message by associating a layout with an appender. The layout determines the format of a log message, while an appender directs the formatted message to its destination. The logging toolkit typically uses PatternLayout to format its log messages. PatternLayout, part of the standard Log4j distribution, lets you specify the output format according to conversion patterns similar to the C language printf
function.
For example, if you invoke PatternLayout with the conversion pattern %-5p%d{DATE} %c{4} %x - %m%n
, a message such as the following is generated:
AUDIT 21 May 2001 11:00:57,109 BEA_WLS_SAMPLE_ADK - admin opened connection to EIS
%-5p
is the priority of the message; in the example shown here, the priority is AUDIT
.%d{DATE}
is the date of the message; in the example shown here, the date is 21 May 2001 11:00:57,109
. %c{4}
is the category for the log message; in the example shown here, the category is BEA_WLS_SAMPLE_ADK
.The text after the dash (-) is the message of the statement.
Listing 5-2 declares a new category for the sample adapter, assigns a priority to the new category, and declares an appender in order to specify the type of file to which log messages should be sent.
Listing 5-2 Sample XML Code for Declaring a New Log Category
<!-
IMPORTANT!!! ROOT Category for the adapter; making this unique prevents other
adapters from logging to your category
-->
<category name='BEA_WLS_SAMPLE_ADK' class='com.bea.logging.LogCategory'>
<!-
Default Priority Level; may be changed at runtime
DEBUG means log all messages from the adapter's code base
INFO means log informationals, warnings, errors, and audits
WARN means log warnings, errors, and audits
ERROR means log errors and audits
AUDIT means log audits only
-->
<priority value='WARN' class='com.bea.logging.LogPriority'/>
<appender-ref ref='WebLogicAppender'/>
</category>
Note: You must specify the class as com.bea.logging.LogCategory
.
Note: The following procedure is based on the assumption that you have cloned a development environment by running the GenerateAdapterTemplate utility. For more information about this utility, see Creating a Custom Development Environment.
To set up the logging framework for your adapter:
WLI_HOME
/adapters/ADAPTER/src/
. Its name includes the .xml
extension. For example, the DBMS sample adapter configuration file is WLI_HOME
/adapters/dbms/src/BEA_WLS_DBMS_ADK.xml
.Listing 5-3 Sample Code for Adding an EventGenerator Log Category with a Priority of DEBUG
<category name='BEA_WLS_DBMS_ADK.EventGenerator'
class='com.bea.logging.LogCategory'>
<priority value='DEBUG'
class='com.bea.logging.LogPriority'/>
</category>
<appender>
element. Instructions within the <layout>
element identify the message format.Note: By default, WebLogicAppender
is used in all sample adapters provided by WebLogic Integration.
Listing 5-4 Sample Code for Adding a File Appender and Layout Pattern
<!-- A basic file appender -->
<appender name='FileAppender'
class='org.apache.Log4j.FileAppender'>
<!-- Send output to a file -->
<param name='File' value='BEA_WLS_DBMS_ADK.log'/>
<!-- Truncate existing -->
<param name="Append" value="true"/>
<!-- Use a basic LOG4J pattern layout -->
<layout class='org.apache.Log4j.PatternLayout'>
<param name='ConversionPattern' value='%-5p %d{DATE} %c{4}
%x - %m%n'/>
</layout>
</appender>
At this point, you should check the setting in the following configuration files:
WLI_HOME
/adapters/
ADAPTER
/src/rar/META-INF/ra.xml
and weblogic-ra.xml
—The AbstractManagedConnectionFactory
uses the logging information entered in the base configuration file to configure the log framework at initialization time.WLI_HOME
/adapters/
ADAPTER
/src/war/web-inf/web.xml
—The RequestHandler
(the parent of AbstractDesignTimeRequestHandler
) uses the logging information entered in the base configuration file to configure the log framework at initialization time.In the preceding paths, ADAPTER
represents the name of your adapter. For example, the name of the DBMS sample adapter appears in the pathname for the associated configuration file, as follows:
WLI_HOME
/adapters/dbms/src/rar/META-INF/ra.xml
In addition to understanding the basic concepts of the logging framework, you also need to understand the three main classes provided in the logging toolkit:
This class is the main interface to the logging framework. It provides numerous convenience methods for logging messages.
The How to Set Up Logging procedure explains how you can configure logging in the base log configuration file. You can also configure logging programmatically by implementing the following logging methods:
logger.setPriority("DEBUG")
changes the minimum priority of messages printed from the current ILogger.logger.addRuntimeDestination (writer)
adds the appender that is used when the container passes its PrintWriter
to the adapter.logger.warn("Some message", true)
logs a message with the priority level of WARN
, without using the ResourceBundle
. The boolean indicates that the string is a message, not a key.logger.warn("someKey")
logs a message with the priority level WARN
, by looking it up with "someKey"
in ResourceBundle
.logger.info("someKey", anObjArray)
logs a message with the priority level of INFO
by looking up a template with someKey
in ResourceBundle
and filling in the blanks with the elements of anObjArray
.logger.error(exception)
logs a message with the priority level of ERROR
, by passing an exception (Throwable) to this method. It calls getMessage()
and includes a stack trace. (All logging methods that take a Throwable as an argument log a stack trace.) This class encapsulates the information needed to identify an ILogger instance in the logging framework. Currently, the LogContext class encapsulates a log category name and a locale, such as en_US
. This class is the primary key for uniquely identifying an ILogger
instance in the log manager.
This class provides a method that allows you to configure the logging framework and gain access to ILogger
instances.
To ensure that you can properly configure the logging toolkit for your adapter, the ADK implements the LogManager
's configure()
method with the arguments shown in Listing 5-5.
Listing 5-5 Sample Code for Configuring the Logging Toolkit
public static LogContext
configure(String strLogConfigFile,
String strRootLogContext,
String strMessageBundleBase,
Locale locale,
ClassLoader classLoader)
Table 5-2 describes the arguments passed by configure()
.
Once the configuration is complete, you can retrieve ILogger
instances for your adapter by supplying a LogContext
object.
Listing 5-6 Sample Code for Supplying a LogContext Object
LogContext logContext = new LogContext("BEA_WLS_SAMPLE_ADK", java.util.Locale.US);
ILogger logger = LogManager.getLogger(logContext); logger.debug("I'm logging now!");
The ADK hides most of the log configuration and setup from you. The com.bea.adapter.spi.AbstractManagedConnectionFactory
class configures the logging toolkit for service connections and the AbstractEventGenerator
configures the logging toolkit for event connections. In addition, all of the Client Connector Interface (CCI) and Service Provider Interface (SPI) base classes included in the ADK provide access to an ILogger
and the LogContext
associated with it.
An adapter may also include layers that support the CCI/SPI layer, such as a socket layer used for establishing communication with the EIS. To make it possible for such adapters to access the correct ILogger
object, you can take either of two approaches:
LogContext
object into the lower layers. This method works, but it adds overhead.LogContext
for the current running thread at the earliest possible place in the code. The ADK's com.bea.adapter.cci.ConnectionFactoryImpl
class sets the LogContext
for the current running thread in the getConnection()
methods. The getConnection()
methods are the first point of contact between a client program and your adapter. Consequently, lower layers in an adapter can safely access the LogContext
for the current running thread by using the following code:Listing 5-7 Code Accessing LogContext for the Current Thread
public static LogContext getLogContext(Thread t)
throws IllegalStateException, IllegalArgumentException
Additionally, we supply the following convenience method on LogManager
:
public static ILogger getLogger() throws IllegalStateException
This method provides an ILogger
for the current running thread. There is one caveat to using this approach: lower layers should not store LogContext
or ILogger
as members. Rather, they should dynamically retrieve them from LogManager. An IllegalStateException
is thrown if this method is called before a LogContext is set for the current running thread.
Internationalization (I18N) and localization (L10N) are central concepts to the ADK logging framework. All logging convenience methods on the ILogger
interface, except the debug methods, allow I18N. The implementation follows the Java Internationalization standards, using ResourceBundle
objects to store locale-specific messages or templates. Sun Microsystems provides a good online tutorial on using the I18N and L10N standards of the Java language.
Most real-world systems must manage multiple clients simultaneously. In a typical multithreaded implementation of such a system, different threads handle different clients. Logging is especially well suited to tracing and debugging complex distributed applications. A common way of differentiating between the logging output of two clients is to instantiate a separate category for each client. This approach has a drawback however: categories proliferate and the overhead required to manage them increases.
A lighter technique is to stamp each log request initiated from the same client interaction with a unique identifier. Neil Harrison describes this method in "Patterns for Logging Diagnostic Messages" in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997).
To stamp each request with a unique identifier, the user pushes contextual information into the Nested Diagnostic Context (NDC). The logging toolkit provides a separate interface for accessing NDC methods. The interface is retrieved from the ILogger by using the getNDCInterface()
method.
NDC printing is turned on in the XML configuration file (with the symbol %x
). Every time a log request is made, the appropriate logging framework component includes the entire NDC stack for the current thread in the log output. The user does not need to intervene in this process. In fact, the user is responsible only for placing the correct information in the NDC by using the push and pop methods at a few well-defined points in the code.
public void someAdapterMethod(String aClient) {
ILogger logger = getLogger();
INestedDiagnosticContext ndc = logger.getNDCInterface();
// I'm keeping track of this client name for all log messages
ndc.push("User name=" + aClient);
// method body
ndc.pop();
}
A good place to use the NDC is in your adapter's CCI Interaction
object.