| /* |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * On initialization, {@code LogManager} reads its configuration from a |
| * properties file, which by default is the "lib/logging.properties" in the JRE |
| * directory. |
| * <p> |
| * However, two optional system properties can be used to customize the initial |
| * configuration process of {@code LogManager}. |
| * <ul> |
| * <li>"java.util.logging.config.class"</li> |
| * <li>"java.util.logging.config.file"</li> |
| * </ul> |
| * <p> |
| * 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. |
| * <p> |
| * 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}. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * The global logging properties may include: |
| * <ul> |
| * <li>"handlers". This property's values should be a list of class names for |
| * handler classes separated by whitespace, these classes must be subclasses of |
| * {@code Handler} and each must have a default constructor, these |
| * classes will be loaded, instantiated and registered as handlers on the root |
| * {@code Logger} (the {@code Logger} named ""). These |
| * {@code Handler}s maybe initialized lazily.</li> |
| * <li>"config". The property defines a list of class names separated by |
| * whitespace. Each class must have a default constructor, in which it can |
| * update the logging configuration, such as levels, handlers, or filters for |
| * some logger, etc. These classes will be loaded and instantiated during |
| * {@code LogManager} configuration</li> |
| * </ul> |
| * <p> |
| * This class, together with any handler and configuration classes associated |
| * with it, <b>must</b> be loaded from the system classpath when |
| * {@code LogManager} configuration occurs. |
| * <p> |
| * 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. |
| * <p> |
| * 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". |
| * <p> |
| * 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". |
| * <p> |
| * 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<String, Logger> 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<String, Logger>(); |
| 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 |
| * <p> |
| * 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 <i>applications</i> |
| * to maintain references to them. |
| * </p> |
| * |
| * @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<Logger> 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<String> 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. |
| * <p> |
| * Notice : No {@code PropertyChangeEvent} are fired. |
| * </p> |
| * |
| * @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<Logger> 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} |
| * <p> |
| * Notice : No {@code PropertyChangeEvent} are fired. |
| * </p> |
| * |
| * @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. |
| * |
| * <p>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<String> 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); |
| } |
| } |
| } |
| } |
| } |