package org.dresdenocl.logging; import java.net.URL; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Hierarchy; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Plugin; import org.osgi.framework.BundleContext; import org.dresdenocl.logging.appender.ErrorLogAppender; import org.dresdenocl.logging.internal.DefaultLogManager; import org.dresdenocl.logging.internal.StandaloneLogManager; /** * The LoggingPlugin integrates log4j and allows client plugins to retrieve an * {@link ILogManager} object via the {@link #getLogManager(Plugin)} method. * Having a separate log manager (and an associated log4j {@link Hierarchy}) for * each client plugin is necessary for a clean integration with the Eclipse * architecture where each plugin has its own classloader, resources and * {@link ILog} instance. * * <p> * The code for this plugin is loosely based on the article * <em>"Plugging in a logging framework for * Eclipse plug-ins"</em> by Manoel Marques available at the IBM developerWorks * site. Large portions of the code and its behaviour have been changed, some * aspects removed and some features added. * </p> * * <p> * To use the LoggingPlugin, simply retrieve an {@link ILogManager} for your * plugin like this: * * <pre> * * * * * * * * * * * * ILogManager logManager = LoggingPlugin.getLogManager(MyPlugin.getDefault()); * </pre> * * You can then get your log4j loggers from the <code>ILogManager</code> using * the familiar <code>getLogger()</code> methods. You can also combine * everything into one call like this: * * <pre> * * * * * * * * * * * Logger logger = LoggingPlugin.getLogManager(MyPlugin.getDefault()).getLogger( * MyClass.class); * </pre> * * An alternative is to provide a facade method in your Plugin class in order * not to introduce dependencies to the LoggingPlugin class in all your classes. * Then you can simply call: * * <pre> * * * * * * * * * * * * * * Logger logger = MyPlugin.getLogger(MyClass.class); * </pre> * * and the <code>getLogger()</code> method in <code>MyPlugin</code> will deal * with getting the <code>ILogManager</code>. See the Javadoc for * {@link ILogManager} for details on how to customize the log configuration * used by the log manager. * </p> * * <p> * Alternatively, you can entirely do without the <code>ILogManager</code> API * and retrieve your loggers the traditional log4j way: * * <pre> * * * * * * * * * * * * * Logger logger = Logger.getLogger(MyClass.class); * </pre> * * This way, a default log4j configuration provided by the * <code>LoggingPlugin</code> will be used and all logs will appear under the * <code>org.dresdenocl.logging</code> plugin id. See * {@link #configureDefaultLogging(Plugin)} for more information on how to * customize logging in this case. * </p> * * @author Matthias Braeuer * @version 1.0 14/03/2007 */ public class LoggingPlugin extends Plugin { // an identifier for the default log manager (used if null is passed when // requesting a log mgr.) private static final String DEFAULT_LOG_MANAGER_NAME = "org.dresdenocl.logging"; //$NON-NLS-1$ // the system property used to activate debug logging for the logging plugin private static final String DEBUG_SYSTEM_PROPERTY = "org.dresdenocl.logging.debug"; //$NON-NLS-1$ // the default name of the log4j configuration file private static final String DEFAULT_LOG4J_CONFIGURATION_FILE = "log4j.properties"; //$NON-NLS-1$ // The plug-in ID public static final String ID = "org.dresdenocl.logging"; //$NON-NLS-1$ // The shared instance private static LoggingPlugin plugin; // maps plugins to their associated log managers private Map<String, ILogManager> logManagers; // used in standalone-mode to configure logging via a single properties file private URL loggerPropertiesUrl; /** * Creates a new <code>LoggingPlugin</code> instance. */ public LoggingPlugin() { plugin = this; } /** * Creates a new {@link LoggingPlugin} instance. Use this constructor when * developing stand-alone applications. * * @param loggerPropertiesUrl * points to a Log4j properties file */ public LoggingPlugin(URL loggerPropertiesUrl) { this(); this.loggerPropertiesUrl = loggerPropertiesUrl; } /* * (non-Javadoc) * * @see * org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext) */ @Override public void start(final BundleContext context) throws Exception { super.start(context); checkIfDebugEnabled(); initializeLogManagers(); } /** * Checks a system property whether debugging should be activated for the * internal log4j logger. */ private void checkIfDebugEnabled() { String debug = System.getProperty(DEBUG_SYSTEM_PROPERTY); if (debug != null && debug.equalsIgnoreCase("true")) { //$NON-NLS-1$ Logger.getRootLogger().setLevel(Level.DEBUG); } } /** * Create the map of log managers and initialize the default log manager for * the logging plugin */ private void initializeLogManagers() { logManagers = new HashMap<String, ILogManager>(); logManagers .put(DEFAULT_LOG_MANAGER_NAME, new DefaultLogManager(plugin)); } /** * Stops the plugin. All log managers registered with the * <code>LoggingPlugin</code> will be disposed. * * @param context * the context for this plugin * * @throws Exception * if an error occurs * * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext) * @see ILogManager#dispose() * */ @Override public void stop(final BundleContext context) throws Exception { disposeLogManagers(); plugin = null; super.stop(context); } /** * Returns the shared instance. * * @return the Singleton plugin or <code>null</code> if the plugin has not * been activated */ public static LoggingPlugin getDefault() { return plugin; } /** * Returns the {@link ILogManager} for a particular plugin. If the given * <code>plugin</code> is <code>null</code>, the default log manager * belonging to the <code>LoggingPlugin</code> will be returned. * * @param aPlugin * the plugin, may be <code>null</code> * * @return the <code>ILogManager</code> for the given plugin or the default * log manager */ public static ILogManager getLogManager(final Plugin aPlugin) { // inside Eclipse if (Platform.isRunning()) { ILogManager logManager; String name; // invariant check if (LoggingPlugin.plugin == null) { throw new IllegalStateException( "The LoggingPlugin has not been activated by the Eclipse platform."); //$NON-NLS-1$ } // determine the name for the log manager name = (aPlugin != null) ? aPlugin.getBundle().getSymbolicName() : DEFAULT_LOG_MANAGER_NAME; // try to get a log manager for this plugin logManager = LoggingPlugin.plugin.logManagers.get(name); // no log manager found, so create a new log manager for the given // plugin if (logManager == null) { logManager = new DefaultLogManager(aPlugin); synchronized (LoggingPlugin.plugin.logManagers) { LoggingPlugin.plugin.logManagers.put(name, logManager); } } return logManager; } // stand-alone application else { // invariant check if (LoggingPlugin.plugin == null) { System.out .println("The LoggingPlugin has not been activated. Intialized it manually."); //$NON-NLS-1$ plugin = new LoggingPlugin(); } final String key = "standaloneLogger"; if (plugin.logManagers == null) plugin.logManagers = new HashMap<String, ILogManager>(1); if (plugin.logManagers.containsKey(key)) return plugin.logManagers.get(key); else { ILogManager standaloneLogManager = new StandaloneLogManager( plugin.loggerPropertiesUrl); plugin.logManagers.put(key, standaloneLogManager); return standaloneLogManager; } } } /** * This method allows to configure the default log4j logger hierarchy that * is automatically created when using one of the <code>getLogger()</code> * methods in the log4j {@link Logger} class. Because Eclipse carefully * separates the classloaders for each plugin, this is the only way a client * plugin can configure the default log4j loggers (loaded by the * <code>org.dresdenocl.logging</code> bundle). This might be necessary * when a client plugin does not want to use the {@link #getLogManager()} * API provided by the <code>LoggingPlugin</code>. * * <p> * The <code>LoggingPlugin</code> provides a default * <em>log4j.properties</em> configuration file which will be used in this * case. This file sets the log level to {@link Level#WARN WARN}. It also * defines an {@link ErrorLogAppender} named <code>"errorLog"</code>, but * does not assign it to the root logger. This is to prevent the Error Log * from being flooded with messages if clients set the log level for their * loggers to {@link Level#DEBUG DEBUG} during development. * </p> * * <p> * Client plugins can contribute their own log properties through a * <em>log4j.properties</em> file that is either located in their root path * or accessible via the plugin's classloader. In this file, they may also * use the Error Log appender defined by the <code>LoggingPlugin</code>. If * a custom <code>log4j.properties</code> file is provided, this method will * load it and configure the default log4j logger hierarchy. * </p> * * @param aPlugin * the plugin that provides additional log4j properties */ public static void configureDefaultLogging(final Plugin aPlugin) { if (Platform.isRunning()) { URL url; // precondition check if (aPlugin == null) { throw new IllegalArgumentException( "The parameter 'plugin' was null!"); //$NON-NLS-1$ } // try to load a file from the given plugin's root url = aPlugin.getBundle().getEntry( "/" + DEFAULT_LOG4J_CONFIGURATION_FILE); //$NON-NLS-1$ // try to load a resource from the plugin if (url == null) { url = aPlugin.getBundle().getResource( DEFAULT_LOG4J_CONFIGURATION_FILE); } // configure additional properties if (url != null) { PropertyConfigurator.configure(url); } } // no else. } // disposes all currently registered log managers private void disposeLogManagers() { synchronized (logManagers) { for (ILogManager logManager : logManagers.values()) { logManager.dispose(); } } } }