null
When building an RCP application, you may need to include a third party code library that uses a common logger API to generate log messages. It may not be practical to modify the library to use the Eclipse ILog API. Therefore, an implementation of the logger library must be incorporated into your application.
One approach is to embed a logger library into the plug-ins that use it. This approach creates some problems especially when plug-ins from multiple contributors are brought together into a single RCP application:
First, memory and disk usage is unnecessarily consumed. Memory is used due to a copy of the library in each plug-in's classloader. The library's static data, such as configuration, is not shared. Multiple copies overuse disk space, and application download time is affected, too.
Second, there is opportunity for confusion at runtime. For example, a LOG4J Appender instantiated by one plug-in will not be usable by another plug-in's LOG4J embbeded library. This occurrence will generate a VM exception. Also, to resolve import directives, Eclipse will non-deterministically select any plug-in that exports the logger packages.
Third, plug-ins that embed a logger library pre-empt an RCP application developer's logger decision. Requirements and design goals of the application, not the plug-in, should drive the decision as to which logger framework should be used. If contributing plug-ins include their own copy of a framework, they have configured logging too early. RCP application deployers should be free to decide which framework to use at packaging time, without a dependency on the plug-in developer's decisions. However, without available logger plug-ins, contributing plug-in developers are forced to make this decision. If logger facade plug-ins were available, plug-in developers would assume that the deployers can get the logging framework they choose.
Fourth, in order to create an extensible LOG4J plug-in, Eclipse Classloading buddy policy must be utilized. If not, then Appenders provided by later plug-ins will not be discovered. In order to extend JDK1.4 logging, the Handler implementation must be instantiated outside of LogManager and bound to Loggers. Otherwise, they will not be found. These issues are easy to resolve, but let's solve it once in a single plug-in, not for every plug-in that carries a logger implementation.
This article aims to help RCP application and plug-in developers avoid the above problems. A group of logger plug-ins are presented and constructed. These plug-ins can become part of every plug-in developers toolbox. The article considers Apache's Logging LOG4J, Sun's JDK 1.4 Logging, Eclipse logging framework using org.eclipse.core.runtime.ILog, Apache's Jakarta Commons Logging, and the Simple Logging Framework for Java logger libraries, and shows how these can be built into modular plug-ins and be combined in any manner as may be required.
In the first section, creating plug-ins from common logger frameworks and facade libraries will be described. A logger facade plug-in upon a custom logging framework, such as PDE ILog API is developed. Then for LOG4J and the JDK 1.4 Logger frameworks a strategy for controlling logger configuration in a single file is presented. Finally, in a demonstration of the flexibility of these plug-ins, instructions for swapping one plug-in for another is given.
This article is not a tutorial about Eclipse plug-in development. If you are new to Eclipse, you can find better introductory articles elsewhere. However, if you have read those introductory articles and are looking for examples of developing plug-ins, this article can be useful. It contains some examples of plug-in development features, including:
This article assumes that the reader has configured and used a logger framework, either LOG4J or JDK 1.4 Logging, so little explanation of how these work will be provided. The article also assumes the reader has knowledge of logger facade libraries like JCL.
Eclipse 3.1 was used to implement these plug-ins.
In this section, we will create the plug-ins for logger frameworks and logger facade API.
Converting a code library into a plug-in is non-destructive. The only difference between a regular code library and a code library plug-in is the plug-in's jar's MANIFEST.MF file contains OSGi directives. You will not have to modify or compile any of the provider's source code. Eclipse does all the work which is to unwrap the provider's distribution jar file and to re-jar the contents with an edited MANIFEST.MF file.
Before you do this for any code library, be sure to read the library's license. Make sure you understand and adhere to the terms of the license.
I did not upload these framework or facade library plug-ins. For the most part, they are exactly identical to their binary distributions. I have provided only the MANIFEST.MF files which you can copy and paste into a plug-in editor as needed. My hope is that the logger framework providers will incorporate these manifest directives into their archive, and so distribute them as ready to use Eclipse plug-ins, and so rendering these next steps an academic exercise.
In this section we will create code library plug-ins for LOG4J and NLOG4J logger frameworks. JDK 1.4 Logging is also a logger framework, but it is not possible to create a plug-in for it because it is already built into the J2SE runtime.
LOG4J is an Apache project and is a popular logger framework.
Here are the steps to create the LOG4J plug-in:
dist/lib/log4j-1.2.12.jar
A few comments about the value of the properties selected for this plugin: In order to communicate to a plug-in developer that the binary code in this plug-in is completely untouched and identical to the Apache distribution, we do what we can to let it be known that the producer of the library is Apache, not us (all we have done here is provide some META-INF/MANIFEST.MF data fields). We use Apache's version number, attribute Apache's LOG4J team, and even name the plug-in from Apache's java package name.
Eclipse will churn away for a few moments as it explodes the archive into your filesystem. Then, the plug-in editor appears. The Apache log4j license requires us to include the license text when redistributing, so before doing anything else, import the license file from the LOG4J's root distribution folder into the expanded META-INF folder of the plug-in.
Eclipse classloader architecture will normally prevent this LOG4J plug-in from finding logger Handlers in dependent plug-ins. Eclipse's class finding algorithm looks for classes in required and imported plug-ins, and the java runtime, and at the last, it looks for classes in its Buddy plug-ins. In order for LOG4J to find Handlers within its dependents, the LOG4J plug-in must have a Classloading Buddy Policy, and if the policy is 'registered', which we will use here, dependents must declare themselves to be a Buddy of this plug-in. This is the purpose of this line in the MANIFEST.MF file:
Eclipse-BuddyPolicy: registered
Buddy classloading is not without performance cost and not without risk of finding the wrong class with the right name. In the case of the LOG4J plug-in, there will likely be only a handful of Appenders to search for, and only at the framework's configuration time.
Here is the final MANIFEST.MF file for LOG4J.
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Log4j Plug-in Bundle-SymbolicName: org.apache.log4j Bundle-Version: 1.2.12 Bundle-ClassPath: . Bundle-Vendor: Apache Software Foundation Bundle-Localization: plugin Export-Package: org.apache.log4j, org.apache.log4j.spi Eclipse-BuddyPolicy: registered
LOG4J contains many more exportable packages. For our purpose here, we don't need any of them so they are not exported.
NLOG4J is a direct replacement for LOG4J. You can create a plug-in for this library by following almost exactly the same steps as for LOG4J (use different values in the wizard's properties).
Here is the MANIFEST.MF file for NLOG4J.
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: NLOG4J Code Library Plug-in Bundle-SymbolicName: org.nlog4j Bundle-Version: 1.2.18 Bundle-ClassPath: . Bundle-Vendor: slf4j.org Bundle-Localization: plugin Export-Package: org.apache.log4j, org.apache.log4j.spi, Eclipse-BuddyPolicy: registered
The NLOG4J archive also contains an implementation of SLF4J, which we cover next. Greater flexibility is gained if we put these in separate plug-ins.
In this section we will create Logging Facade API plug-ins each of which will export both the Jakarta Commons Logging (JCL) and SLF4J logger APIs. If a developer uses either of these API for log messages, his product would be isolated from the logger framework.
We use SLF4J's implementation of JCL in order to avoid confrontation with Apache's JCL's dynamic discovery mechanism which is based on class discovery. I don't know whether Eclipse classloading implementation and Apache's discovery are operable together, but I don't have to. SLF4J provides a simple implementation of the same interface.
These facade plug-ins we create are interchangeable. As a result, a JCL/SLF4J facade plug-in for LOG4J can replace a facade plug-in for JDK1.4 (and vice versa) for example. The logger framework used in an application is determined by which variant of the SLF4J plug-in is installed and enabled.
After you download and install the SLF4J distribution, take a look at the libraries in the install directory.
/
jcl104-over-slf4j.jar
slf4j-jdk14.jar
slf4j-log4j12.jar
slf4j-log4j13.jar
slf4j-nop.jar
slf4j-simple.jar
Create a plug-in for each of the SLF4J variants that you expect to be useful. If you would like to use the NLOG4J variant, you have to extract the code library from the NLOG4J distribution. Do Not bundle more than one slf4j-*.jar file in any plug-in. Create each plug-in in the same steps used in creating the LOG4J plug-in. Remember to include the JCL compatibility library in every one of these plug-ins.
The plug-in naming convention - naming by the java package name - is broken in this case because slf4j.org produces multiple libraries having the org.slf4j package name. Use the framework name as a discriminator as in the following naming pattern:
Note that according to the slf4j.org documentation, LOG4J 1.2 and LOG4J 1.3 alpha have runtime (not buildtime) differences and so separate SLF4J implementations are necessary. As a result, declare a 'required' dependency in these SLF4J plug-ins. 'Required' dependencies can only be satisfied when the named plug-in is installed and enabled at runtime. Using the LOG4J version number in the dependency list will prevent accidental use of the wrong version of LOG4J.
Here are the MANIFEST.MF files for SLF4J plug-ins which were used for prototyping the ideas of this article. Please observe, as in the case for Apache's LOG4J plug-in, names and versions reflect the releases from the provider, as is right and proper.
For LOG4J 1.2:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: SLF4j Log4j 1.2 Code Library Plug-in Bundle-SymbolicName: org.slf4j.log4j12 Bundle-Version: 1.0.0.rc1 Bundle-ClassPath: . Bundle-Localization: plugin Export-Package: org.apache.commons.logging, org.apache.commons.logging.impl, org.slf4j, org.slf4j.impl, org.slf4j.spi Bundle-Vendor: slf4j.org Require-Bundle: org.apache.log4j;bundle-version="1.2.0"
For JDK1.4 Logging:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: SLF4J Jdk14 Code Library Plug-in Bundle-SymbolicName: org.slf4j.jdk14 Bundle-Version: 1.0.0.rc1 Bundle-ClassPath: . Bundle-Localization: plugin Export-Package: org.apache.commons.logging, org.apache.commons.logging.impl, org.slf4j, org.slf4j.impl, org.slf4j.spi Bundle-Vendor: slf4j.org
For SimpleLog (distributed with SLF4J):
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: SLF4j Simple Code Library Plug-in Bundle-SymbolicName: org.slf4j.simple Bundle-Version: 1.0.0.rc1 Bundle-ClassPath: . Bundle-Vendor: slf4j.org Bundle-Localization: plugin Export-Package: org.apache.commons.logging, org.apache.commons.logging.impl, org.slf4j, org.slf4j.impl, org.slf4j.spi
For NOP logger (a null logger implementation provided by SLF4J):
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: SLF4j NOP Library Plug-in Bundle-SymbolicName: org.slf4j.nlog4j Bundle-Version: 1.0.0.rc1 Bundle-ClassPath: . Bundle-Vendor: slf4j.org Bundle-Localization: plugin Export-Package: org.apache.commons.logging, org.apache.commons.logging.impl, org.slf4j, org.slf4j.impl, org.slf4j.spi
For NLOG4J:
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: SLF4j NLOG4J Library Plug-in Bundle-SymbolicName: org.slf4j.nlog4j Bundle-Version: 1.0.0.rc1 Bundle-ClassPath: . Bundle-Vendor: slf4j.org Bundle-Localization: plugin Export-Package: org.apache.commons.logging, org.apache.commons.logging.impl, org.slf4j, org.slf4j.impl, org.slf4j.spi
Custom logger frameworks can also be wrapped with the SLF4J library. This is done easily. The plug-in discussed here wraps PDE's logger framework. It is available here: slf4j-pde plug-in jar.
To create a new SLF4J Logger, the SLF4J web page directs us to copy
SLF4J source code, and implement the following interfaces: org.slf4j.spi.Logger,
org.slf4j.spi.LoggerFactory and to provide implementations of org.slf4j.impl.StaticLoggerBind
and org.slf4j.impl.StaticMarkerBind.
In the custom Logger implementation, each of the log methods will
create an IStatus and log it to a plug-in's ILog using the ILog.log(IStatus
s) method. They map the Logger level to appropriate IStatus
severity.
One problem to resolve: Each plug-in has its own ILog implementation and the right one has to be chosen. For example, the Logger "org.openproject.ui" should send log messages to the org.openproject plug-in's ILog not the SLF4J plug-in's ILog. To resolve, an extension point is defined in the SLF4J plug-in. The org.openproject plug-in will define the extension and SLF4J will use the extension to map to the plug-in's ILog. Here is the extension point definition:
<extension point="org.slf4j.pde.loggers">
<loggerMapEntry regex="^org.openproject.ui\..*" symbolicName="org.openproject.ui"/>
</extension>
The above extension declaration will map any Logger whose name matches the regular expression "regex" to the ILog of the plug-in whose symbolic name is "symbolicName" field.
There are additional features of this plug-in which can be implemented:
Marker to provide a code
value. Marker is a parameter on the Logger methods and is
described as a decoration. Code is a property in IStatus
which is conveyed by ILog. Presumably, Code can be used to more
precisely map the warning or error message to a course of action for
the user.The logger library plug-ins are now ready. In the next section, we'll show how a client plug-in would use them.
In this section we'll demonstrate how to use the logger plug-ins by providing a short JUnit test plug-in. The plug-in and source is available for download.
Here is the JUnit test case we intend to make succeed.
public class LoggerCase1 extends LoggerTestCase {
public void testSLF4JLog() {
org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(LoggerCase1.class);
String msg = "log from slf4j";
log.info(msg);
assertTrue(isLogged(msg));
}
public void testApacheJCLLog() {
org.apache.commons.logging.Log log = LogFactory.getLog(LoggerCase1.class.getName());
String msg = "log from Apache Commons log";
log.info(msg);
assertTrue(isLogged(msg));
}
}
The LoggerTestCase class implements JUnit's TestCase.setUp()
and TestCase.tearDown() which register/deregister as a
listener to the test plug-in's ILog. LoggerTestCase.isLogged(String
msg) will return true if the message was received while it was
listening.
This client plug-in imports, not requires, the SLF4J plug-ins. Import means that any plug-in that exports these packages are suitable for the test. If 'requires' is used, then the flexibility to select the SLF4J plug-in at configuration will be forfeited.
This section shows aspects of using the LOG4J framework, including specifying the path of the configuration file, and a sample implementation of a LOG4J Appender which writes to PDE's ILog API.
In the logTest plug-in's source directory, find a file named log4j.properties
with the following content:
# Set root logger level to DEBUG and its only appender to A1. log4j.rootLogger=DEBUG, A1 # A1 is set to be my plugin log appender. log4j.appender.A1=logTest.PluginLogAppender log4j.appender.A1.SymbolicName =logTest # A1 uses PatternLayout. log4j.appender.A1.layout=org .apache.log4j.PatternLayout log4j.appender.A1.layout .ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
This file configures log4j to send messages to extension class PluginLogAppender.
For the LOG4J plug-in to find the file, its location is specified on the test plug-ins startup command line.
-Dlog4j.configuration=/bin/log4j.properties . This
vm option defines the LOG4J configuration over all LOG4J
implementations in the application: the one in the LOG4J plug-in and in
any plug-ins that bundle the LOG4J implementation.The logTest.PluginLogAppender extends from LOG4J's org.apache.log4j.AppenderSkelet.
The Appender has a configurable property, symbolicName, which provides
the symbolic name of the plug-in whose ILog is used to output the log
message. The Platform class has methods used to resolve the
Bundle and to obtain its ILog:
private ILog getBundleILog() {
// get the bundle for a plug-in
Bundle b = Platform.getBundle(getSymbolicName());
if(b == null) {
this.errorHandler.error(String.format("Plugin: %s not found in %s.", getSymbolicName(), this.name));
return null;
}
return Platform.getLog(b);
}
This appender also converts LOG4J Log Level to an IStatus severity:
private int getSeverity(LoggingEvent ev) {
Level level = ev.getLevel();
if(level == Level.FATAL || level == Level.ERROR)
return IStatus.ERROR;
else if(level == Level.WARN)
return IStatus.WARNING;
else if(level == Level.INFO)
return IStatus.INFO;
else // debug, trace and custom levels
return IStatus.OK;
}
In the Appender's append method, the above methods are used to build
an org.eclipse.core.runtime.Status and pass it to the
Bundle's ILog.
LOG4J will read the configuration file and load the named Appender class. However, the Appender class will not be found by the plug-in's classloader until it consults its buddy policy which is 'registered'. The classloader iterates through the list of registered buddies for the first buddy that can return the named class. Once found, the class is loaded, an object is instantiated and logging begins.
Therefore, the plug-in that contains the Appender has the following line in its MANIFEST.MF file:
Eclipse-RegisterBuddy: org.apache.log4j, org.nlog4j
Both log4j and nlog4j plug-ins are named here because either may be looking for this Appender.
When you run the JUnit test case, the following will appear in the ILog file:

This section shows aspects of using the JDK 1.4 Logging Framework, including specifying the location of the configuration file, and classloading a sample implementation of a Handler which writes to PDE's ILog.
In JDK Logging, the configuration file is named logging.properties. Here are the contents of the logging.properties file in the test case plug-in:
.level= FINE logTest.PluginLogHandler.level=FINE logTest.PluginLogHandler .formatter = java.util.logging.SimpleFormatt er logTest.Jdk14LogTest.level=FINE
Specifying the file's location is also done using a Java VM property, as in the LOG4J case.
-Djava.util.logging.config.file=/bin/logging.properties .
This vm option defines the configuration file used for all
plugins which use JDK Logging.After having walked through the interesting parts of a LOG4J Appender, the same for the JDK Handler are simply not as interesting, so I won't review them here. What is interesting is how the our custom Handler class is found and instantiated.
The class that reads the logging configuration and instantiates
Handlers is java.utils.logging.LogManager. It is loaded by
the System Classloader. This handler class is inside the logTest plug-in
and is not visible to the System Classloader. Hence, configuration
fails. You may have noticed that the logging.properties above does not
have a 'handlers' property set. You might reasonably expect this to be
set to logTest.PluginLogHandler. This would be ineffective
in the Eclipse runtime. Even Buddy policy can't help in this case.
As a workaround, the handler class can be instantiated in the plug-in's activation method. This works because when the plug-in looks for a class, Eclipse find classes that are local to the caller's the plug-in. Here is an implementation of a start method that loads the Handler and binds it to a Logger.
public void start(BundleContext context) throws Exception {
super.start(context);
// handler instantiated in my plugin will see Platform
PluginLogHandler h = new PluginLogHandler(context.getBundle().getSymbolicName());
Logger log = Logger.getLogger(context.getBundle().getSymbolicName());
log.addHandler(h);
}
When the JUnit test case is run, the following will appear in the log:

Any of the SLF4J plug-ins are usable by the logTest plug-in. This section shows how you can swap the plug-ins in your workspace allowing you to run the unit tests under any and all. Hopefully, this will prove the flexibility of using logger facade plug-ins, specifically SLF4J's.
If you use the LOG4J variant SLF4J plug-in, be sure to configure the LOG4J framework. Likewise if you use the JDK Logging variant. No additional configuration is necessary if you use the SimpleLog, NOP, or custom PDE SLF4J variants.
Swapping works because these SLF4J plug-ins all export the same packages, and because the plug-ins that depend on them 'import' the packages (instead of 'requiring' them).
To swap these plug-ins in your workspace, simply close one plug-in project and open another. If you use a target environment during development, remove the old plug-in from the target directory, copy in the new plug-in to the target directory, revisit the target preference page and reload the directory.
Don't forget this rule: Be sure to have only one active at a time.
There is an unexpected behavior while swapping plug-ins in the developer's workspace. I have submitted it to the eclipse team. See Bugzilla: 115961 for more information and workarounds.
As you can see, creating plug-ins for logger libraries is not hard. As a plug-in or RCP developer, you can build them yourself for your needs. It would be more convenient and encouraging if these were available from logger library providers.
Hopefully, if you have to wrap a code library with different functionality, this article has been of some help in highlighting the relevant Eclipse plug-in development features.
Consider these last thoughts as you decide which plug-ins to use.