Traditionally, logging in IBM TDI is accomplished by means of Server- or task (AssemblyLine)-based Appenders, which rely upon the Apache Log4J framework to do the actual log output. In TDI 7.1, the hardwired link between TDI and Log4J is severed, and replaced with a configurable logging class which by default invokes Log4J. While Log4J provides a variety of output channels and formats, there are other logging utilities with overlapping and additional output channels that you as a TDI user may need. Many of these are open source libraries that are not bundled with TDI for legal reasons. To enable inclusion of these 3rd party logging utilities, the TDI logging component is modeled to act as a proxy between TDI and the actual logging implementations, called LogInterface implementations. TDI comes with implementations for Log4J, JLOG and java.util.log, as follows:
Logging Utility | Handlers/Appenders |
---|---|
Apache Log4J |
Category based configuration *) |
Standard Java Logging (java.util.log) | FileHandler |
JLOG |
Category based configuration *) |
*) Category based configuration means that the configuration of the logger is defined in an external file specific to the logging utility such as "log4j.properties" for Log4J.
The TDI configuration file structure accommodates a top-level folder which holds com.ibm.di.config.interfaces.LogConfigItem objects. This folder is populated by the TDI class loader (IDILoader) scanning all jar and zip files for "tdi.xml" files. The "tdi.xml" files define new loggers that become available to the TDI user/CE by including appropriate sections. Each logger defined this way will also include a form definition that the TDI CE can present to the user for configuration of its custom logger parameters.
The com.ibm.di.log.Log class is designed to use one or more of these new log components. Each log component implements com.ibm.di.log.LogInterface. This class is responsible for mapping LogInterface methods to the corresponding methods in its logging utility framework. The log component is given the LogConfigItem object to properly configure its back end logger when it is instantiated.
The TDI logging interface consists of the following files and objects:
Object | Description |
---|---|
com.ibm.di.config.interfaces.LogConfigItem | This is the configuration object for a defined LogInterface implementation. |
com.ibm.di.log.LogInterface | This is the interface implemented by loggers that provide access to 3rd party logging utilities. |
com.ibm.di.server.Log | This is the utility class used by TDI components to create the Log object. |
<workdir>/logging.categories | Optional file to map categories to LogInterface class names. This file is not present by default. |
com.ibm.di.log.TDILog4j | The LogInterface implementation for Log4J. |
<installdir>/etc/log4j.properties | The log4j configuration file for TDI main component categories (server,ce and config drivers). |
<installdir>/etc/global.properties | A property is defined to globally enable/disable
log activities. When false, all log calls made through the TDI Log
class will be discarded.
The property name is "com.ibm.di.logging.enabled". |
TDI components obtain loggers by creating an instance of the com.ibm.di.server.Log class using a category to determine the actual logging utility and output to use. This is done to preserve backward compatibility.
The Log class will also consult the logging.categories file to see if there is a mapping between the category and a LogInterface class. By default there are no specific mappings in this file (it is not present by default) and the Log class falls back on the TDILog4J implementation.
The logging interface will look for a file called logging.categories in the working directory to override use of the default TDILog4J LogInterface implementation. Each line in this file contains a category name with a value giving the java class name of the LogInterface implementation.
For example:
*:com.ibm.di.log.TDILog4j AssemblyLine.AssemblyLines/myAL:com.ibm.di.log.TDILogJUL
In the above example all AssemblyLines will log using the TDILog4j framework, except for the AssemblyLine named myAL that will use TDILogJUL. The logging utilities currently available are:
Logging utilities are typically configured using an external configuration file. For example, Log4J uses a property style file where names (categories) are mapped to specific output types/formats (for example, file output, XML format). Users then ask for a logger instance providing a category name, which in turn is resolved by the logging utility consulting its configuration file.
Each LogInterface implementation may require a properties file to provide correct configuration of its loggers. Specifically, if the default TDILog4j implementation is replaced by another logging utility, the new logging utility should provide loggers for those categories that are defined in the released version of the etc/log4j.properties file.
The main components in TDI use category names to obtain loggers (server, CE, config drivers and so forth) and rely on the external configuration to provide details about each logger. However, users can specify additional loggers using the configuration editor. The user may add loggers at the AssemblyLine level and/or at the server level. When the user adds a logger this way, the details about the logger is stored in the TDI configuration file. The instantiation of the logger is now handled by TDI and not by the logging facility and its external configuration file. Traditionally, TDI had a list of predefined Log4J loggers the user could choose from (see Table 91). In IBM TDI 7.1, the hard coded list of loggers has been externalized into the system namespace (Loggers folder).
Logger configurations are stored in the top-level folder Loggers in the configuration file. System wide available loggers are defined in the system namespace, which is built from tdi.xml files when TDI starts.
The tdi.xml files found in jar/zip files in the installdir/jars directory (and other custom loader directories) are added to the system namespace by the TDI loader (IDILoader). A logging component is defined by two separate sections in this file.
The loggers section defines the LogInterface implementation and parameters that designate a specific logger for a specific logging utility (for example, log4j, file logger). This section also contains a second parameter that points to a form definition used to present the configurable parameters for the user. Additional parameters may appear in this section specific to each logger.
<Logger name="ibmdi.JavaUtilLoggingFile"> <parameter name="categoryBased">false</parameter> <parameter name="com.ibm.di.formName">ibmdi.JavaUtilLoggingFile</parameter> <parameter name="com.ibm.di.log.interface">com.ibm.di.log.TDILogJUL</parameter> <parameter name="handler">FileHandler</parameter> </Logger>
The form section defines the configurable parameters for the logger.
<Form name="ibmdi.JavaUtilLoggingFile"> <FormItemNames> <ListItem>fileName</ListItem> <ListItem>formatter</ListItem> ... other parameters... </FormItemNames> <FormItem name="fileName"> <parameter name="description">The pattern for the log file name</parameter> <parameter name="label">File Name</parameter> <parameter name="Required">true</parameter> <parameter name="script">selectFile</parameter> <parameter name="scriptLabel">Select...</parameter> <parameter name="scripthelp">Choose the file name to use</parameter> </FormItem> <FormItem name="formatter"> <Values> <ListItem>Simple</ListItem> <ListItem>XML</ListItem> </Values> <parameter name="description">Choose a SimpleFormatter or a XMLFormatter</parameter> <parameter name="label">Formatter</parameter> <parameter name="syntax">droplist</parameter> </FormItem> .... Other FormItem definitions </Form>
The overall syntax for the tdi.xml file would be something like the following skeleton example:
<?xml version="1.0" encoding="UTF-8"?> <MetamergeConfig> <Folder name="Loggers"> <Logger name="..."> </Logger> </Folder> <Folder name="Forms"> <Form name="..."> </Form> </Folder> </MetamergeConfig>
Adding a new logging utility to TDI involves creating a LogInterface implementation and providing a tdi.xml file with proper sections (see section Logger Internal Configuration). The implementation must also provide a static method to bootstrap a new logger.
The main logging class in TDI has two methods defined to govern the logging activity on a global basis. The initial setting of the activity is defined by a property named com.ibm.di.logging.enabled. Logging that bypasses this class will not be affected.
/** * Disables or enables TDI logging. All loggers are affected by this setting. */ public static void setLoggingEnabled(boolean enabled); /** * Returns whether TDI logging is active or disabled. */ public static boolean isLoggingEnabled(boolean enabled);
When logging is turned off from the start (for example, property is set to false) there may still be a few lines logged by TDI during initialization of the loader and the main program. If we want to remove logging completely, we should modify the logging utility's configuration file (for example, log4j.properties, jlog.properties etc).
package com.ibm.di.log; /** * Defines an Interface to new Loggers. * Any Logger we use must adhere to this interface. * The Implementation must provide a public constructor with no arguments. * After construction either the setCategory() or the addAppender() method will be called. */ public interface LogInterface { public final static String TYPE = "type"; public final static String NAME = "name"; public final static String CONFIG_INSTANCE = "configInstance"; public final static String TIME = "time"; /** * Set the category for this Logger. * This method specifies a category, to allow a category based configuration. * * @param category The category to use. */ public void setCategory(String category) throws Exception; /** * Add an Appender to the Logger using the given config. Appender is the * org.apache.log4j name, java.util.logging would call it a Handler. May * throw an Exception if the config does not make sense.<br/> * The params Map may contain these keys to help set up the Appender: * * - TYPE: "AssemblyLine", "EventHandler" or "" * - NAME: A String with the name of component * - CONFIG_INSTANCE: a RSInterface * - TIME: a String with the time in milliseconds * * * @param config * The LogConfigItem. * @param params * Extra information that may be useful/ */ public void addAppender(LogConfigItem config, Map params) throws Exception; /** * Log a message with level debug. * @param str The string to be logged */ public void debug( String str ); /** * Log a message with level info. * @param str The string to be logged */ public void info( String str ); /** * Log a message with level warning. * @param str The string to be logged */ public void warn( String str ); /** * Log a message with level error. * @param str The string to be logged */ public void error( String str ); /** * Log a message with level error, and an additional Throwable. * @param str The string to be logged * @param error The Throwable to be logged */ public void error( String str, Throwable error ); /** * Log a message with level fatal. * @param str The string to be logged */ public void fatal ( String str); /** * Log a message with level fatal, and an additional Throwable. * @param str The string to be logged * @param error The Throwable to be logged */ public void fatal ( String str, Throwable error ); /** * Log a message with the specified level. * @param level The level to use when logging. * @param str The string to be logged */ public void log (String level, String str ); /** * Check if a debug message would be logged. * @return true if a debug message might be logged */ public boolean isDebugEnabled (); /** * Free up all resources this logger uses. * The logger will not be called anymore. */ public void close(); }
TDI components that require logging capabilities should obtain a logger through the com.ibm.di.server.Log class. This class is a proxy between the client and the actual logging implementation. When TDI components add logging to their code, it should decide whether to reuse the existing logging categories that are predefined, create a new category or maintain its own LogConfigItem configuration object. In either case, it should end up using a Log object to do the actual logging.
You typically use one of the two constructors to configure the logger. After the logger has been created we can use the setPrefix() method to set a string which is prefixed to all outgoing messages. The prefix string is not translated.
The category name is used to configure the logger. This is defined in the properties file for the logging utility in use. The resourceFileName is the name of a resource (loaded by com.ibm.di.server.ResourceHash) that contains an NLS table used when translating messages.
/** * Create a log object using category as both the name of the resource * file and the logger category name (configuration). */ public Log(String category); /** * Create a log object using separate values for category and resource name. */ public Log(String category, String resourceFileName); /** * Sets a prefix to be prefixed to all messages * * @param prefix */ public void setPrefix (String prefix);
There is a set of logging methods for the various levels provided as a convenience.
public void log<level>(String msg)
where <level> is the logging level:
These methods will log the message (including any prefix) as-is to the logger.
log.setPrefix("PRE"); log.loginfo("Hello"); >> PRE Hello
When we need to translate log messages we should use the following methods:
where resid is the resource identifier in the resource file associated with the Log object. If a translation for the resource id isn't found, the resource identifier is used as-is in the log output. Each of these methods comes in four variants to let you supply values for substitution markers in the translated string. When we use one of the variants with substitution values, the Log class will use java.util.text.MessageFormat on the string with the parameters you provide. Three methods let you provide one, two or an arbitrary number of substitution values.
public void debug(String res) public void debug(String res, Object param) public void debug(String res, Object param1, Object param2) public void debug(String res, Object[] params)
If we want to log our own error message with a Throwable object, we can use the method error(String resid, Throwable error).
If we want to generate an exception with a translated message, we can use the method exception(String resid) throws Exception which will throw a generic java.lang.Exception object with the translated string as its message.
If we want to translate a string to use somewhere else, we can use the getString() methods to obtain the translated string from the resource file associated with the log object.
This very simple example shows how to log an NLS message based on a properties file to the standard TDI server log (miserver). It is assumed that "XXX.properties" is packaged with the code.
Contents of "XXX.properties":
my.resource.id= Hello World
import com.ibm.di.server.Log; public class XXX() { public XXX() { this.log = new Log("miserver";, "XXX"); this.log.info("my.resource.id"); } }
The above should result in a log message "Hello World" written to the TDI server log.