Browsing all articles tagged with log4j
Apr
19

Using Log4J in Eclipse RCP (and forcing all other plugins to use it too!)

There are many resources out there that describe the process (and headache!) of using Apache’s Log4J http://logging.apache.org/log4j/ logging framework within an Eclipse rich client platform (RCP) project.  After digesting all the resources that the internet has to offer (my favorite links at the end) and adding log4j to the RCP project that I am currently working on, the most common problems seem to be:

  • Getting log4j into the environment
  • Classloader errors caused by different versions of log4j classes being loaded by different plugins
  • Problems accessing/locating the log4j.properties file

I also had a problem where I wanted to use only Log4J for logging, so I wanted to be sure that all log messages from all plugins in my RCP project were being picked up by the logging framework.

A log4j bundle is available in the Eclipse Orbit builds (http://download.eclipse.org/tools/orbit/downloads/drops/R20100519200754/); so, actually getting log4j into the RCP environment just involves downloading that bundle and requiring it in the RCP project.  However, there is still the potential for classloader problems and dependency issues when multiple plugins are developed as part of a project and each brings its own log4j package to the table.  The solution is somewhat simple:  designate one plugin project to initialize and supply the logging bundle, and have all the other plugins rely on this.  The choice of the master plugin is usually fairly simple;  for my project it is the RCP project itself, which actually initializes the application environment and has a dependency on the feature plugins.  That master plugin should have the log4j as a bundle dependency (Required Plug-ins), and the other plugins should simply list org.apache.log4j as an imported package.

To configure the logger using a .properties file, a bit of additional work is needed.  Many resources seem to imply there is a way to accomplish this that results in Log4j automatically loading the properties (because it will search for the log4j.properties file at startup if no config is provided), but I wasn’t able to get that to work and didn’t want to spend that much time one it.  My solution was to include log4j.properties in the project directory of the RCP plugin, make sure to add this file to the binary build under build configuration, and initialize the logger using this properties file when the application starts.  This initialization is done by adding the following to your Activator class in the master plugin:

import java.net.URL;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.eclipse.core.runtime.FileLocator;

final private static Logger log = Logger.getLogger(Activator.class);

public void start(final BundleContext context) throws Exception {
	// [...]
	// Setup logging
	URL confURL = getBundle().getEntry("log4j.properties");
	PropertyConfigurator.configure( FileLocator.toFileURL(confURL).getFile());
	log.info("Logging using log4j and configuration " + FileLocator.toFileURL(confURL).getFile());
	hookPluginLoggers(context); // You need to add this method to hook other plugins, described later...
	// [...]
}

That will load and configure the logger using the properties file. The properties file can be modified without needing a re-compile.  To actually log from any class in any plugin now, just add the following:


public class MyClassInAnyPlugin {
	private static org.apache.log4j.Logger log = org.apache.log4j.Logger
			.getLogger(MyClassInAnyPlugin.class);
	void someMethod() {
		log.debug("A debug message");
		log.warn("A warning message");
	}
}

A description of all the log methods and levels can be found in the log4j docs at the official site.  At this point, you should have logging up and running in your project, but we have yet to ensure that _all_ log messages will be sent through log4j.  In particular, other Eclipse plugins use the Eclipse logging framework; without special hooking, these messages will be missed by log4j.  To accomplish that, I used the PluginLogListener class described at http://www.ibm.com/developerworks/library/os-eclog/ in order to add a log listener to each loaded plugin.  To add a listener for each plugin, we need to add the following method (and field):

import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;

final private List<PluginLogListener> pluginLogHooks = new ArrayList<PluginLogListener>();

// Hook all loaded bundles into the log4j framework
private void hookPluginLoggers(final BundleContext context) {
	for (Bundle bundle : context.getBundles()){
		ILog pluginLogger = Platform.getLog(bundle);
		pluginLogHooks.add(new PluginLogListener(pluginLogger,
				Logger.getLogger(bundle.getSymbolicName())));
		log.trace("Added logging hook for bundle: " + bundle.getSymbolicName());
	}
}

Even though we don’t ever need to read the contents of the pluginLogHooks list, it’s very important to store the PluginLogListener instances we create; without persistent storage they will be reclaimed by the garbage collection process and their logging hooks will be removed.  After adding this method, when your application starts up, you should see something like this:

0    [main] INFO  com.rtsync.devs.rcp.Activator  (Activator.java:76): Logging using log4j and configuration [...]/my.project.rcp/log4j.properties
3    [main] TRACE com.rtsync.devs.rcp.Activator  (Activator.java:103): Added logging hook for bundle: org.eclipse.osgi
4    [main] TRACE com.rtsync.devs.rcp.Activator  (Activator.java:103): Added logging hook for bundle: com.ibm.icu
5    [main] TRACE com.rtsync.devs.rcp.Activator  (Activator.java:103): Added logging hook for bundle: org.apache.commons.logging
5    [main] TRACE com.rtsync.devs.rcp.Activator  (Activator.java:103): Added logging hook for bundle: org.eclipse.ant.core
6    [main] TRACE com.rtsync.devs.rcp.Activator  (Activator.java:103): Added logging hook for bundle: org.eclipse.compare.core
6    [main] TRACE com.rtsync.devs.rcp.Activator  (Activator.java:103): Added logging hook for bundle: org.eclipse.core.commands[...]

Now, any regular log message created by any plugin in your RCP project will be caught and interpreted by the log4j framework, which looks like this:

!ENTRY org.eclipse.core.resources 2 10035 2011-04-19 10:02:03.710!MESSAGE The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes.
819  [main] WARN  org.eclipse.core.resources  (PluginLogListener.java:91): org.eclipse.core.resources - 10035 - The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes.

Note that as long as the -consoleLog option is used with Eclipse (which is added by default to your run/debug configurations) you’ll see the original plugin log message and then the Log4J log message.  Now you have the entire environment using the same logging framework, whether it wants to or not!

Good in-depth explanations of logging in Eclipse RCP:

 

%d bloggers like this: