1 /* LogManager.java -- a class for maintaining Loggers and managing
2 configuration properties
3 Copyright (C) 2002, 2005, 2006 Free Software Foundation, Inc.
5 This file is part of GNU Classpath.
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library. Thus, the terms and
24 conditions of the GNU General Public License cover the whole
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module. An independent module is a module which is not derived from
34 or based on this library. If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so. If you do not wish to do so, delete this
37 exception statement from your version. */
40 package java.util.logging;
42 import gnu.classpath.SystemProperties;
44 import java.beans.PropertyChangeListener;
45 import java.beans.PropertyChangeSupport;
46 import java.io.ByteArrayInputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.lang.ref.WeakReference;
51 import java.util.Collections;
52 import java.util.Enumeration;
53 import java.util.HashMap;
54 import java.util.Iterator;
55 import java.util.List;
57 import java.util.Properties;
58 import java.util.StringTokenizer;
61 * The <code>LogManager</code> maintains a hierarchical namespace
62 * of Logger objects and manages properties for configuring the logging
63 * framework. There exists only one single <code>LogManager</code>
64 * per virtual machine. This instance can be retrieved using the
65 * static method {@link #getLogManager()}.
67 * <p><strong>Configuration Process:</strong> The global LogManager
68 * object is created and configured when the class
69 * <code>java.util.logging.LogManager</code> is initialized.
70 * The configuration process includes the subsequent steps:
73 * <li>If the system property <code>java.util.logging.manager</code>
74 * is set to the name of a subclass of
75 * <code>java.util.logging.LogManager</code>, an instance of
76 * that subclass is created and becomes the global LogManager.
77 * Otherwise, a new instance of LogManager is created.</li>
78 * <li>The <code>LogManager</code> constructor tries to create
79 * a new instance of the class specified by the system
80 * property <code>java.util.logging.config.class</code>.
81 * Typically, the constructor of this class will call
82 * <code>LogManager.getLogManager().readConfiguration(java.io.InputStream)</code>
83 * for configuring the logging framework.
84 * The configuration process stops at this point if
85 * the system property <code>java.util.logging.config.class</code>
86 * is set (irrespective of whether the class constructor
87 * could be called or an exception was thrown).</li>
89 * <li>If the system property <code>java.util.logging.config.class</code>
90 * is <em>not</em> set, the configuration parameters are read in from
91 * a file and passed to
92 * {@link #readConfiguration(java.io.InputStream)}.
93 * The name and location of this file are specified by the system
94 * property <code>java.util.logging.config.file</code>.</li>
95 * <li>If the system property <code>java.util.logging.config.file</code>
96 * is not set, however, the contents of the URL
97 * "{gnu.classpath.home.url}/logging.properties" are passed to
98 * {@link #readConfiguration(java.io.InputStream)}.
99 * Here, "{gnu.classpath.home.url}" stands for the value of
100 * the system property <code>gnu.classpath.home.url</code>.</li>
103 * <p>The <code>LogManager</code> has a level of <code>INFO</code> by
104 * default, and this will be inherited by <code>Logger</code>s unless they
105 * override it either by properties or programmatically.
107 * @author Sascha Brawer (brawer@acm.org)
109 public class LogManager
112 * The object name for the logging management bean.
115 public static final String LOGGING_MXBEAN_NAME
116 = "java.util.logging:type=Logging";
119 * The singleton LogManager instance.
121 private static LogManager logManager;
124 * The singleton logging bean.
126 private static LoggingMXBean loggingBean;
129 * The registered named loggers; maps the name of a Logger to
130 * a WeakReference to it.
132 private Map<String, WeakReference<Logger>> loggers;
135 * The properties for the logging framework which have been
138 private Properties properties;
141 * A delegate object that provides support for handling
142 * PropertyChangeEvents. The API specification does not
143 * mention which bean should be the source in the distributed
144 * PropertyChangeEvents, but Mauve test code has determined that
145 * the Sun J2SE 1.4 reference implementation uses the LogManager
146 * class object. This is somewhat strange, as the class object
147 * is not the bean with which listeners have to register, but
148 * there is no reason for the GNU Classpath implementation to
149 * behave differently from the reference implementation in
152 private final PropertyChangeSupport pcs = new PropertyChangeSupport( /* source bean */
155 protected LogManager()
157 loggers = new HashMap();
161 * Returns the globally shared LogManager instance.
163 public static synchronized LogManager getLogManager()
165 if (logManager == null)
167 logManager = makeLogManager();
173 private static final String MANAGER_PROPERTY = "java.util.logging.manager";
175 private static LogManager makeLogManager()
177 String managerClassName = SystemProperties.getProperty(MANAGER_PROPERTY);
178 LogManager manager = (LogManager) createInstance
179 (managerClassName, LogManager.class, MANAGER_PROPERTY);
181 manager = new LogManager();
185 private static final String CONFIG_PROPERTY = "java.util.logging.config.class";
187 private static void initLogManager()
189 LogManager manager = getLogManager();
190 Logger.root.setLevel(Level.INFO);
191 manager.addLogger(Logger.root);
193 /* The Javadoc description of the class explains
194 * what is going on here.
196 Object configurator = createInstance(System.getProperty(CONFIG_PROPERTY),
197 /* must be instance of */ Object.class,
202 if (configurator == null)
203 manager.readConfiguration();
205 catch (IOException ex)
207 /* FIXME: Is it ok to ignore exceptions here? */
212 * Registers a listener which will be notified when the
213 * logging properties are re-read.
215 public synchronized void addPropertyChangeListener(PropertyChangeListener listener)
217 /* do not register null. */
220 pcs.addPropertyChangeListener(listener);
224 * Unregisters a listener.
226 * If <code>listener</code> has not been registered previously,
227 * nothing happens. Also, no exception is thrown if
228 * <code>listener</code> is <code>null</code>.
230 public synchronized void removePropertyChangeListener(PropertyChangeListener listener)
232 if (listener != null)
233 pcs.removePropertyChangeListener(listener);
237 * Adds a named logger. If a logger with the same name has
238 * already been registered, the method returns <code>false</code>
239 * without adding the logger.
241 * <p>The <code>LogManager</code> only keeps weak references
242 * to registered loggers. Therefore, names can become available
243 * after automatic garbage collection.
245 * @param logger the logger to be added.
247 * @return <code>true</code>if <code>logger</code> was added,
248 * <code>false</code> otherwise.
250 * @throws NullPointerException if <code>name</code> is
253 public synchronized boolean addLogger(Logger logger)
255 /* To developers thinking about to remove the 'synchronized'
256 * declaration from this method: Please read the comment
257 * in java.util.logging.Logger.getLogger(String, String)
258 * and make sure that whatever you change wrt. synchronization
259 * does not endanger thread-safety of Logger.getLogger.
260 * The current implementation of Logger.getLogger assumes
261 * that LogManager does its synchronization on the globally
262 * shared instance of LogManager.
267 /* This will throw a NullPointerException if logger is null,
268 * as required by the API specification.
270 name = logger.getName();
272 ref = loggers.get(name);
275 if (ref.get() != null)
278 /* There has been a logger under this name in the past,
279 * but it has been garbage collected.
284 /* Adding a named logger requires a security permission. */
285 if ((name != null) && ! name.equals(""))
288 Logger parent = findAncestor(logger);
289 loggers.put(name, new WeakReference<Logger>(logger));
290 if (parent != logger.getParent())
291 logger.setParent(parent);
293 // The level of the newly added logger must be specified.
294 // The easiest case is if there is a level for exactly this logger
295 // in the properties. If no such level exists the level needs to be
296 // searched along the hirachy. So if there is a new logger 'foo.blah.blub'
297 // and an existing parent logger 'foo' the properties 'foo.blah.blub.level'
298 // and 'foo.blah.level' need to be checked. If both do not exist in the
299 // properties the level of the new logger is set to 'null' (i.e. it uses the
300 // level of its parent 'foo').
301 Level logLevel = logger.getLevel();
302 String searchName = name;
303 String parentName = parent != null ? parent.getName() : "";
304 while (logLevel == null && ! searchName.equals(parentName))
306 logLevel = getLevelProperty(searchName + ".level", logLevel);
307 int index = searchName.lastIndexOf('.');
309 searchName = searchName.substring(0,index);
313 logger.setLevel(logLevel);
315 /* It can happen that existing loggers should be children of
316 * the newly added logger. For example, assume that there
317 * already exist loggers under the names "", "foo", and "foo.bar.baz".
318 * When adding "foo.bar", the logger "foo.bar.baz" should change
319 * its parent to "foo.bar".
321 for (Iterator iter = loggers.keySet().iterator(); iter.hasNext();)
323 Logger possChild = (Logger) ((WeakReference) loggers.get(iter.next()))
325 if ((possChild == null) || (possChild == logger)
326 || (possChild.getParent() != parent))
329 if (! possChild.getName().startsWith(name))
332 if (possChild.getName().charAt(name.length()) != '.')
335 possChild.setParent(logger);
342 * Finds the closest ancestor for a logger among the currently
343 * registered ones. For example, if the currently registered
344 * loggers have the names "", "foo", and "foo.bar", the result for
345 * "foo.bar.baz" will be the logger whose name is "foo.bar".
347 * @param child a logger for whose name no logger has been
350 * @return the closest ancestor for <code>child</code>,
351 * or <code>null</code> if <code>child</code>
352 * is the root logger.
354 * @throws NullPointerException if <code>child</code>
355 * is <code>null</code>.
357 private synchronized Logger findAncestor(Logger child)
359 String childName = child.getName();
360 int childNameLength = childName.length();
361 Logger best = Logger.root;
362 int bestNameLength = 0;
367 if (child == Logger.root)
370 for (String candName : loggers.keySet())
372 candNameLength = candName.length();
374 if (candNameLength > bestNameLength
375 && childNameLength > candNameLength
376 && childName.startsWith(candName)
377 && childName.charAt(candNameLength) == '.')
379 cand = loggers.get(candName).get();
380 if ((cand == null) || (cand == child))
383 bestNameLength = candName.length();
392 * Returns a Logger given its name.
394 * @param name the name of the logger.
396 * @return a named Logger, or <code>null</code> if there is no
397 * logger with that name.
399 * @throw java.lang.NullPointerException if <code>name</code>
400 * is <code>null</code>.
402 public synchronized Logger getLogger(String name)
404 WeakReference<Logger> ref;
406 /* Throw a NullPointerException if name is null. */
409 ref = loggers.get(name);
417 * Returns an Enumeration of currently registered Logger names.
418 * Since other threads can register loggers at any time, the
419 * result could be different any time this method is called.
421 * @return an Enumeration with the names of the currently
422 * registered Loggers.
424 public synchronized Enumeration<String> getLoggerNames()
426 return Collections.enumeration(loggers.keySet());
430 * Resets the logging configuration by removing all handlers for
431 * registered named loggers and setting their level to <code>null</code>.
432 * The level of the root logger will be set to <code>Level.INFO</code>.
434 * @throws SecurityException if a security manager exists and
435 * the caller is not granted the permission to control
436 * the logging infrastructure.
438 public synchronized void reset() throws SecurityException
440 /* Throw a SecurityException if the caller does not have the
441 * permission to control the logging infrastructure.
445 properties = new Properties();
447 Iterator<WeakReference<Logger>> iter = loggers.values().iterator();
448 while (iter.hasNext())
449 for (WeakReference<Logger> ref : loggers.values())
460 else if (logger != Logger.root)
462 logger.resetLogger();
463 logger.setLevel(null);
468 Logger.root.setLevel(Level.INFO);
469 Logger.root.resetLogger();
473 * Configures the logging framework by reading a configuration file.
474 * The name and location of this file are specified by the system
475 * property <code>java.util.logging.config.file</code>. If this
476 * property is not set, the URL
477 * "{gnu.classpath.home.url}/logging.properties" is taken, where
478 * "{gnu.classpath.home.url}" stands for the value of the system
479 * property <code>gnu.classpath.home.url</code>.
481 * <p>The task of configuring the framework is then delegated to
482 * {@link #readConfiguration(java.io.InputStream)}, which will
483 * notify registered listeners after having read the properties.
485 * @throws SecurityException if a security manager exists and
486 * the caller is not granted the permission to control
487 * the logging infrastructure, or if the caller is
488 * not granted the permission to read the configuration
491 * @throws IOException if there is a problem reading in the
492 * configuration file.
494 public synchronized void readConfiguration()
495 throws IOException, SecurityException
498 InputStream inputStream;
500 path = System.getProperty("java.util.logging.config.file");
501 if ((path == null) || (path.length() == 0))
503 String url = (System.getProperty("gnu.classpath.home.url")
504 + "/logging.properties");
507 inputStream = new URL(url).openStream();
514 // If no config file could be found use a default configuration.
515 if(inputStream == null)
517 String defaultConfig = "handlers = java.util.logging.ConsoleHandler \n"
519 inputStream = new ByteArrayInputStream(defaultConfig.getBytes());
523 inputStream = new java.io.FileInputStream(path);
527 readConfiguration(inputStream);
531 // Close the stream in order to save
532 // resources such as file descriptors.
537 public synchronized void readConfiguration(InputStream inputStream)
538 throws IOException, SecurityException
540 Properties newProperties;
544 newProperties = new Properties();
545 newProperties.load(inputStream);
547 this.properties = newProperties;
548 keys = newProperties.propertyNames();
550 while (keys.hasMoreElements())
552 String key = ((String) keys.nextElement()).trim();
553 String value = newProperties.getProperty(key);
558 value = value.trim();
560 if ("handlers".equals(key))
562 StringTokenizer tokenizer = new StringTokenizer(value);
563 while (tokenizer.hasMoreTokens())
565 String handlerName = tokenizer.nextToken();
566 Handler handler = (Handler)
567 createInstance(handlerName, Handler.class, key);
568 Logger.root.addHandler(handler);
572 if (key.endsWith(".level"))
574 String loggerName = key.substring(0, key.length() - 6);
575 Logger logger = getLogger(loggerName);
579 logger = Logger.getLogger(loggerName);
585 level = Level.parse(value);
587 catch (IllegalArgumentException e)
589 warn("bad level \'" + value + "\'", e);
593 logger.setLevel(level);
599 /* The API specification does not talk about the
600 * property name that is distributed with the
601 * PropertyChangeEvent. With test code, it could
602 * be determined that the Sun J2SE 1.4 reference
603 * implementation uses null for the property name.
605 pcs.firePropertyChange(null, null, null);
609 * Returns the value of a configuration property as a String.
611 public synchronized String getProperty(String name)
613 if (properties != null)
614 return properties.getProperty(name);
620 * Returns the value of a configuration property as an integer.
621 * This function is a helper used by the Classpath implementation
622 * of java.util.logging, it is <em>not</em> specified in the
625 * @param name the name of the configuration property.
627 * @param defaultValue the value that will be returned if the
628 * property is not defined, or if its value is not an integer
631 static int getIntProperty(String name, int defaultValue)
635 return Integer.parseInt(getLogManager().getProperty(name));
644 * Returns the value of a configuration property as an integer,
645 * provided it is inside the acceptable range.
646 * This function is a helper used by the Classpath implementation
647 * of java.util.logging, it is <em>not</em> specified in the
650 * @param name the name of the configuration property.
652 * @param minValue the lowest acceptable value.
654 * @param maxValue the highest acceptable value.
656 * @param defaultValue the value that will be returned if the
657 * property is not defined, or if its value is not an integer
658 * number, or if it is less than the minimum value,
659 * or if it is greater than the maximum value.
661 static int getIntPropertyClamped(String name, int defaultValue,
662 int minValue, int maxValue)
664 int val = getIntProperty(name, defaultValue);
665 if ((val < minValue) || (val > maxValue))
671 * Returns the value of a configuration property as a boolean.
672 * This function is a helper used by the Classpath implementation
673 * of java.util.logging, it is <em>not</em> specified in the
676 * @param name the name of the configuration property.
678 * @param defaultValue the value that will be returned if the
679 * property is not defined, or if its value is neither
680 * <code>"true"</code> nor <code>"false"</code>.
682 static boolean getBooleanProperty(String name, boolean defaultValue)
686 return (Boolean.valueOf(getLogManager().getProperty(name))).booleanValue();
695 * Returns the value of a configuration property as a Level.
696 * This function is a helper used by the Classpath implementation
697 * of java.util.logging, it is <em>not</em> specified in the
700 * @param propertyName the name of the configuration property.
702 * @param defaultValue the value that will be returned if the
703 * property is not defined, or if
704 * {@link Level#parse(java.lang.String)} does not like
705 * the property value.
707 static Level getLevelProperty(String propertyName, Level defaultValue)
711 String value = getLogManager().getProperty(propertyName);
713 return Level.parse(getLogManager().getProperty(propertyName));
724 * Returns the value of a configuration property as a Class.
725 * This function is a helper used by the Classpath implementation
726 * of java.util.logging, it is <em>not</em> specified in the
729 * @param propertyName the name of the configuration property.
731 * @param defaultValue the value that will be returned if the
732 * property is not defined, or if it does not specify
733 * the name of a loadable class.
735 static final Class getClassProperty(String propertyName, Class defaultValue)
737 String propertyValue = logManager.getProperty(propertyName);
739 if (propertyValue != null)
742 return locateClass(propertyValue);
744 catch (ClassNotFoundException e)
746 warn(propertyName + " = " + propertyValue, e);
752 static final Object getInstanceProperty(String propertyName, Class ofClass,
755 Class klass = getClassProperty(propertyName, defaultClass);
761 Object obj = klass.newInstance();
762 if (ofClass.isInstance(obj))
765 catch (InstantiationException e)
767 warn(propertyName + " = " + klass.getName(), e);
769 catch (IllegalAccessException e)
771 warn(propertyName + " = " + klass.getName(), e);
774 if (defaultClass == null)
779 return defaultClass.newInstance();
781 catch (java.lang.InstantiationException ex)
783 throw new RuntimeException(ex.getMessage());
785 catch (java.lang.IllegalAccessException ex)
787 throw new RuntimeException(ex.getMessage());
792 * An instance of <code>LoggingPermission("control")</code>
793 * that is shared between calls to <code>checkAccess()</code>.
795 private static final LoggingPermission controlPermission = new LoggingPermission("control",
799 * Checks whether the current security context allows changing
800 * the configuration of the logging framework. For the security
801 * context to be trusted, it has to be granted
802 * a LoggingPermission("control").
804 * @throws SecurityException if a security manager exists and
805 * the caller is not granted the permission to control
806 * the logging infrastructure.
808 public void checkAccess() throws SecurityException
810 SecurityManager sm = System.getSecurityManager();
812 sm.checkPermission(controlPermission);
816 * Creates a new instance of a class specified by name and verifies
817 * that it is an instance (or subclass of) a given type.
819 * @param className the name of the class of which a new instance
822 * @param type the object created must be an instance of
823 * <code>type</code> or any subclass of <code>type</code>
825 * @param property the system property to reference in error
828 * @return the new instance, or <code>null</code> if
829 * <code>className</code> is <code>null</code>, if no class
830 * with that name could be found, if there was an error
831 * loading that class, or if the constructor of the class
832 * has thrown an exception.
834 private static final Object createInstance(String className, Class type,
839 if ((className == null) || (className.length() == 0))
844 klass = locateClass(className);
845 if (type.isAssignableFrom(klass))
846 return klass.newInstance();
847 warn(property, className, "not an instance of " + type.getName());
849 catch (ClassNotFoundException e)
851 warn(property, className, "class not found", e);
853 catch (IllegalAccessException e)
855 warn(property, className, "illegal access", e);
857 catch (InstantiationException e)
859 warn(property, className, e);
861 catch (java.lang.LinkageError e)
863 warn(property, className, "linkage error", e);
869 private static final void warn(String property, String klass, Throwable t)
871 warn(property, klass, null, t);
874 private static final void warn(String property, String klass, String msg)
876 warn(property, klass, msg, null);
879 private static final void warn(String property, String klass, String msg,
882 warn("error instantiating '" + klass + "' referenced by " + property +
883 (msg == null ? "" : ", " + msg), t);
887 * All debug warnings go through this method.
890 private static final void warn(String msg, Throwable t)
892 System.err.println("WARNING: " + msg);
894 t.printStackTrace(System.err);
898 * Locates a class by first checking the system class loader and
899 * then checking the context class loader.
901 * @param name the fully qualified name of the Class to locate
902 * @return Class the located Class
905 private static Class locateClass(String name) throws ClassNotFoundException
907 ClassLoader loader = Thread.currentThread().getContextClassLoader();
910 return Class.forName(name, true, loader);
912 catch (ClassNotFoundException e)
914 loader = ClassLoader.getSystemClassLoader();
915 return Class.forName(name, true, loader);
920 * Return the logging bean. There is a single logging bean per
924 public static synchronized LoggingMXBean getLoggingMXBean()
926 if (loggingBean == null)
928 loggingBean = new LoggingMXBean()
930 public String getLoggerLevel(String logger)
932 LogManager mgr = getLogManager();
933 Logger l = mgr.getLogger(logger);
936 Level lev = l.getLevel();
939 return lev.getName();
942 public List getLoggerNames()
944 LogManager mgr = getLogManager();
945 // This is inefficient, but perhaps better for maintenance.
946 return Collections.list(mgr.getLoggerNames());
949 public String getParentLoggerName(String logger)
951 LogManager mgr = getLogManager();
952 Logger l = mgr.getLogger(logger);
961 public void setLoggerLevel(String logger, String level)
963 LogManager mgr = getLogManager();
964 Logger l = mgr.getLogger(logger);
966 throw new IllegalArgumentException("no logger named " + logger);
971 newLevel = Level.parse(level);
972 l.setLevel(newLevel);