/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.util.logging; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; import java.util.StringTokenizer; import libcore.io.IoUtils; /** * {@code LogManager} is used to maintain configuration properties of the * logging framework, and to manage a hierarchical namespace of all named * {@code Logger} objects. *

* There is only one global {@code LogManager} instance in the * application, which can be get by calling static method * {@link #getLogManager()}. This instance is created and * initialized during class initialization and cannot be changed. *

* The {@code LogManager} class can be specified by * java.util.logging.manager system property, if the property is unavailable or * invalid, the default class {@link java.util.logging.LogManager} will * be used. *

* On initialization, {@code LogManager} reads its configuration from a * properties file, which by default is the "lib/logging.properties" in the JRE * directory. *

* However, two optional system properties can be used to customize the initial * configuration process of {@code LogManager}. *

*

* These two properties can be set in three ways, by the Preferences API, by the * "java" command line property definitions, or by system property definitions * passed to JNI_CreateJavaVM. *

* The "java.util.logging.config.class" should specifies a class name. If it is * set, this given class will be loaded and instantiated during * {@code LogManager} initialization, so that this object's default * constructor can read the initial configuration and define properties for * {@code LogManager}. *

* If "java.util.logging.config.class" property is not set, or it is invalid, or * some exception is thrown during the instantiation, then the * "java.util.logging.config.file" system property can be used to specify a * properties file. The {@code LogManager} will read initial * configuration from this file. *

* If neither of these properties is defined, or some exception is thrown * during these two properties using, the {@code LogManager} will read * its initial configuration from default properties file, as described above. *

* The global logging properties may include: *

*

* This class, together with any handler and configuration classes associated * with it, must be loaded from the system classpath when * {@code LogManager} configuration occurs. *

* Besides global properties, the properties for loggers and Handlers can be * specified in the property files. The names of these properties will start * with the complete dot separated names for the handlers or loggers. *

* In the {@code LogManager}'s hierarchical namespace, * {@code Loggers} are organized based on their dot separated names. For * example, "x.y.z" is child of "x.y". *

* Levels for {@code Loggers} can be defined by properties whose name end * with ".level". Thus "alogger.level" defines a level for the logger named as * "alogger" and for all its children in the naming hierarchy. Log levels * properties are read and applied in the same order as they are specified in * the property file. The root logger's level can be defined by the property * named as ".level". *

* This class is thread safe. It is an error to synchronize on a * {@code LogManager} while synchronized on a {@code Logger}. */ public class LogManager { /** The shared logging permission. */ private static final LoggingPermission perm = new LoggingPermission("control", null); /** The singleton instance. */ static LogManager manager; /** * The {@code String} value of the {@link LoggingMXBean}'s ObjectName. */ public static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; /** * Get the {@code LoggingMXBean} instance. this implementation always throws * an UnsupportedOperationException. * * @return the {@code LoggingMXBean} instance */ public static LoggingMXBean getLoggingMXBean() { throw new UnsupportedOperationException(); } // FIXME: use weak reference to avoid heap memory leak private Hashtable loggers; /** The configuration properties */ private Properties props; /** the property change listener */ private PropertyChangeSupport listeners; static { // init LogManager singleton instance String className = System.getProperty("java.util.logging.manager"); if (className != null) { manager = (LogManager) getInstanceByClass(className); } if (manager == null) { manager = new LogManager(); } // read configuration try { manager.readConfiguration(); } catch (Exception e) { e.printStackTrace(); } // if global logger has been initialized, set root as its parent Logger root = new Logger("", null); root.setLevel(Level.INFO); Logger.global.setParent(root); manager.addLogger(root); manager.addLogger(Logger.global); } /** * Default constructor. This is not public because there should be only one * {@code LogManager} instance, which can be get by * {@code LogManager.getLogManager()}. This is protected so that * application can subclass the object. */ protected LogManager() { loggers = new Hashtable(); props = new Properties(); listeners = new PropertyChangeSupport(this); // add shutdown hook to ensure that the associated resource will be // freed when JVM exits Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { reset(); } }); } /** * Does nothing. */ public void checkAccess() { } /** * Add a given logger into the hierarchical namespace. The * {@code Logger.addLogger()} factory methods call this method to add newly * created Logger. This returns false if a logger with the given name has * existed in the namespace *

* Note that the {@code LogManager} may only retain weak references to * registered loggers. In order to prevent {@code Logger} objects from being * unexpectedly garbage collected it is necessary for applications * to maintain references to them. *

* * @param logger * the logger to be added. * @return true if the given logger is added into the namespace * successfully, false if the given logger exists in the namespace. */ public synchronized boolean addLogger(Logger logger) { String name = logger.getName(); if (loggers.get(name) != null) { return false; } addToFamilyTree(logger, name); loggers.put(name, logger); logger.setManager(this); return true; } private void addToFamilyTree(Logger logger, String name) { Logger parent = null; // find parent int lastSeparator; String parentName = name; while ((lastSeparator = parentName.lastIndexOf('.')) != -1) { parentName = parentName.substring(0, lastSeparator); parent = loggers.get(parentName); if (parent != null) { setParent(logger, parent); break; } else if (getProperty(parentName + ".level") != null || getProperty(parentName + ".handlers") != null) { parent = Logger.getLogger(parentName); setParent(logger, parent); break; } } if (parent == null && (parent = loggers.get("")) != null) { setParent(logger, parent); } // find children // TODO: performance can be improved here? String nameDot = name + '.'; Collection allLoggers = loggers.values(); for (final Logger child : allLoggers) { Logger oldParent = child.getParent(); if (parent == oldParent && (name.length() == 0 || child.getName().startsWith(nameDot))) { final Logger thisLogger = logger; child.setParent(thisLogger); if (oldParent != null) { // -- remove from old parent as the parent has been changed oldParent.children.remove(child); } } } } /** * Get the logger with the given name. * * @param name * name of logger * @return logger with given name, or {@code null} if nothing is found. */ public synchronized Logger getLogger(String name) { return loggers.get(name); } /** * Get a {@code Enumeration} of all registered logger names. * * @return enumeration of registered logger names */ public synchronized Enumeration getLoggerNames() { return loggers.keys(); } /** * Get the global {@code LogManager} instance. * * @return the global {@code LogManager} instance */ public static LogManager getLogManager() { return manager; } /** * Get the value of property with given name. * * @param name * the name of property * @return the value of property */ public String getProperty(String name) { return props.getProperty(name); } /** * Re-initialize the properties and configuration. The initialization * process is same as the {@code LogManager} instantiation. *

* Notice : No {@code PropertyChangeEvent} are fired. *

* * @throws IOException * if any IO related problems happened. */ public void readConfiguration() throws IOException { // check config class String configClassName = System.getProperty("java.util.logging.config.class"); if (configClassName == null || getInstanceByClass(configClassName) == null) { // if config class failed, check config file String configFile = System.getProperty("java.util.logging.config.file"); if (configFile == null) { // if cannot find configFile, use default logging.properties configFile = System.getProperty("java.home") + File.separator + "lib" + File.separator + "logging.properties"; } InputStream input = null; try { try { input = new FileInputStream(configFile); } catch (IOException exception) { // fall back to using the built-in logging.properties file input = LogManager.class.getResourceAsStream("logging.properties"); if (input == null) { throw exception; } } readConfiguration(new BufferedInputStream(input)); } finally { IoUtils.closeQuietly(input); } } } // use SystemClassLoader to load class from system classpath static Object getInstanceByClass(final String className) { try { Class clazz = ClassLoader.getSystemClassLoader().loadClass(className); return clazz.newInstance(); } catch (Exception e) { try { Class clazz = Thread.currentThread().getContextClassLoader().loadClass(className); return clazz.newInstance(); } catch (Exception innerE) { System.err.println("Loading class '" + className + "' failed"); System.err.println(innerE); return null; } } } // actual initialization process from a given input stream private synchronized void readConfigurationImpl(InputStream ins) throws IOException { reset(); props.load(ins); // The RI treats the root logger as special. For compatibility, always // update the root logger's handlers. Logger root = loggers.get(""); if (root != null) { root.setManager(this); } // parse property "config" and apply setting String configs = props.getProperty("config"); if (configs != null) { StringTokenizer st = new StringTokenizer(configs, " "); while (st.hasMoreTokens()) { String configerName = st.nextToken(); getInstanceByClass(configerName); } } // set levels for logger Collection allLoggers = loggers.values(); for (Logger logger : allLoggers) { String property = props.getProperty(logger.getName() + ".level"); if (property != null) { logger.setLevel(Level.parse(property)); } } listeners.firePropertyChange(null, null, null); } /** * Re-initialize the properties and configuration from the given * {@code InputStream} *

* Notice : No {@code PropertyChangeEvent} are fired. *

* * @param ins * the input stream * @throws IOException * if any IO related problems happened. */ public void readConfiguration(InputStream ins) throws IOException { checkAccess(); readConfigurationImpl(ins); } /** * Reset configuration. * *

All handlers are closed and removed from any named loggers. All loggers' * level is set to null, except the root logger's level is set to * {@code Level.INFO}. */ public synchronized void reset() { checkAccess(); props = new Properties(); Enumeration names = getLoggerNames(); while (names.hasMoreElements()) { String name = names.nextElement(); Logger logger = getLogger(name); if (logger != null) { logger.reset(); } } Logger root = loggers.get(""); if (root != null) { root.setLevel(Level.INFO); } } /** * Add a {@code PropertyChangeListener}, which will be invoked when * the properties are reread. * * @param l * the {@code PropertyChangeListener} to be added. */ public void addPropertyChangeListener(PropertyChangeListener l) { if (l == null) { throw new NullPointerException("l == null"); } checkAccess(); listeners.addPropertyChangeListener(l); } /** * Remove a {@code PropertyChangeListener}, do nothing if the given * listener is not found. * * @param l * the {@code PropertyChangeListener} to be removed. */ public void removePropertyChangeListener(PropertyChangeListener l) { checkAccess(); listeners.removePropertyChangeListener(l); } /** * Returns a named logger associated with the supplied resource bundle. * * @param resourceBundleName the resource bundle to associate, or null for * no associated resource bundle. */ synchronized Logger getOrCreate(String name, String resourceBundleName) { Logger result = getLogger(name); if (result == null) { result = new Logger(name, resourceBundleName); addLogger(result); } return result; } /** * Sets the parent of this logger in the namespace. Callers must first * {@link #checkAccess() check security}. * * @param newParent * the parent logger to set. */ synchronized void setParent(Logger logger, Logger newParent) { logger.parent = newParent; if (logger.levelObjVal == null) { setLevelRecursively(logger, null); } newParent.children.add(logger); logger.updateDalvikLogHandler(); } /** * Sets the level on {@code logger} to {@code newLevel}. Any child loggers * currently inheriting their level from {@code logger} will be updated * recursively. * * @param newLevel the new minimum logging threshold. If null, the logger's * parent level will be used; or {@code Level.INFO} for loggers with no * parent. */ synchronized void setLevelRecursively(Logger logger, Level newLevel) { int previous = logger.levelIntVal; logger.levelObjVal = newLevel; if (newLevel == null) { logger.levelIntVal = logger.parent != null ? logger.parent.levelIntVal : Level.INFO.intValue(); } else { logger.levelIntVal = newLevel.intValue(); } if (previous != logger.levelIntVal) { for (Logger child : logger.children) { if (child.levelObjVal == null) { setLevelRecursively(child, null); } } } } }