Home | History | Annotate | Download | only in logging
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package java.util.logging;
     19 
     20 import java.beans.PropertyChangeListener;
     21 import java.beans.PropertyChangeSupport;
     22 import java.io.BufferedInputStream;
     23 import java.io.File;
     24 import java.io.FileInputStream;
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 import java.util.Collection;
     28 import java.util.Enumeration;
     29 import java.util.Hashtable;
     30 import java.util.Properties;
     31 import java.util.StringTokenizer;
     32 import libcore.io.IoUtils;
     33 
     34 /**
     35  * {@code LogManager} is used to maintain configuration properties of the
     36  * logging framework, and to manage a hierarchical namespace of all named
     37  * {@code Logger} objects.
     38  * <p>
     39  * There is only one global {@code LogManager} instance in the
     40  * application, which can be get by calling static method
     41  * {@link #getLogManager()}. This instance is created and
     42  * initialized during class initialization and cannot be changed.
     43  * <p>
     44  * The {@code LogManager} class can be specified by
     45  * java.util.logging.manager system property, if the property is unavailable or
     46  * invalid, the default class {@link java.util.logging.LogManager} will
     47  * be used.
     48  * <p>
     49  * On initialization, {@code LogManager} reads its configuration from a
     50  * properties file, which by default is the "lib/logging.properties" in the JRE
     51  * directory.
     52  * <p>
     53  * However, two optional system properties can be used to customize the initial
     54  * configuration process of {@code LogManager}.
     55  * <ul>
     56  * <li>"java.util.logging.config.class"</li>
     57  * <li>"java.util.logging.config.file"</li>
     58  * </ul>
     59  * <p>
     60  * These two properties can be set in three ways, by the Preferences API, by the
     61  * "java" command line property definitions, or by system property definitions
     62  * passed to JNI_CreateJavaVM.
     63  * <p>
     64  * The "java.util.logging.config.class" should specifies a class name. If it is
     65  * set, this given class will be loaded and instantiated during
     66  * {@code LogManager} initialization, so that this object's default
     67  * constructor can read the initial configuration and define properties for
     68  * {@code LogManager}.
     69  * <p>
     70  * If "java.util.logging.config.class" property is not set, or it is invalid, or
     71  * some exception is thrown during the instantiation, then the
     72  * "java.util.logging.config.file" system property can be used to specify a
     73  * properties file. The {@code LogManager} will read initial
     74  * configuration from this file.
     75  * <p>
     76  * If neither of these properties is defined, or some exception is thrown
     77  * during these two properties using, the {@code LogManager} will read
     78  * its initial configuration from default properties file, as described above.
     79  * <p>
     80  * The global logging properties may include:
     81  * <ul>
     82  * <li>"handlers". This property's values should be a list of class names for
     83  * handler classes separated by whitespace, these classes must be subclasses of
     84  * {@code Handler} and each must have a default constructor, these
     85  * classes will be loaded, instantiated and registered as handlers on the root
     86  * {@code Logger} (the {@code Logger} named ""). These
     87  * {@code Handler}s maybe initialized lazily.</li>
     88  * <li>"config". The property defines a list of class names separated by
     89  * whitespace. Each class must have a default constructor, in which it can
     90  * update the logging configuration, such as levels, handlers, or filters for
     91  * some logger, etc. These classes will be loaded and instantiated during
     92  * {@code LogManager} configuration</li>
     93  * </ul>
     94  * <p>
     95  * This class, together with any handler and configuration classes associated
     96  * with it, <b>must</b> be loaded from the system classpath when
     97  * {@code LogManager} configuration occurs.
     98  * <p>
     99  * Besides global properties, the properties for loggers and Handlers can be
    100  * specified in the property files. The names of these properties will start
    101  * with the complete dot separated names for the handlers or loggers.
    102  * <p>
    103  * In the {@code LogManager}'s hierarchical namespace,
    104  * {@code Loggers} are organized based on their dot separated names. For
    105  * example, "x.y.z" is child of "x.y".
    106  * <p>
    107  * Levels for {@code Loggers} can be defined by properties whose name end
    108  * with ".level". Thus "alogger.level" defines a level for the logger named as
    109  * "alogger" and for all its children in the naming hierarchy. Log levels
    110  * properties are read and applied in the same order as they are specified in
    111  * the property file. The root logger's level can be defined by the property
    112  * named as ".level".
    113  * <p>
    114  * This class is thread safe. It is an error to synchronize on a
    115  * {@code LogManager} while synchronized on a {@code Logger}.
    116  */
    117 public class LogManager {
    118 
    119     /** The shared logging permission. */
    120     private static final LoggingPermission perm = new LoggingPermission("control", null);
    121 
    122     /** The singleton instance. */
    123     static LogManager manager;
    124 
    125     /**
    126      * The {@code String} value of the {@link LoggingMXBean}'s ObjectName.
    127      */
    128     public static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging";
    129 
    130     /**
    131      * Get the {@code LoggingMXBean} instance. this implementation always throws
    132      * an UnsupportedOperationException.
    133      *
    134      * @return the {@code LoggingMXBean} instance
    135      */
    136     public static LoggingMXBean getLoggingMXBean() {
    137         throw new UnsupportedOperationException();
    138     }
    139 
    140     // FIXME: use weak reference to avoid heap memory leak
    141     private Hashtable<String, Logger> loggers;
    142 
    143     /** The configuration properties */
    144     private Properties props;
    145 
    146     /** the property change listener */
    147     private PropertyChangeSupport listeners;
    148 
    149     static {
    150         // init LogManager singleton instance
    151         String className = System.getProperty("java.util.logging.manager");
    152         if (className != null) {
    153             manager = (LogManager) getInstanceByClass(className);
    154         }
    155         if (manager == null) {
    156             manager = new LogManager();
    157         }
    158 
    159         // read configuration
    160         try {
    161             manager.readConfiguration();
    162         } catch (Exception e) {
    163             e.printStackTrace();
    164         }
    165 
    166         // if global logger has been initialized, set root as its parent
    167         Logger root = new Logger("", null);
    168         root.setLevel(Level.INFO);
    169         Logger.global.setParent(root);
    170 
    171         manager.addLogger(root);
    172         manager.addLogger(Logger.global);
    173     }
    174 
    175     /**
    176      * Default constructor. This is not public because there should be only one
    177      * {@code LogManager} instance, which can be get by
    178      * {@code LogManager.getLogManager()}. This is protected so that
    179      * application can subclass the object.
    180      */
    181     protected LogManager() {
    182         loggers = new Hashtable<String, Logger>();
    183         props = new Properties();
    184         listeners = new PropertyChangeSupport(this);
    185         // add shutdown hook to ensure that the associated resource will be
    186         // freed when JVM exits
    187         Runtime.getRuntime().addShutdownHook(new Thread() {
    188             @Override public void run() {
    189                 reset();
    190             }
    191         });
    192     }
    193 
    194     /**
    195      * Does nothing.
    196      */
    197     public void checkAccess() {
    198     }
    199 
    200     /**
    201      * Add a given logger into the hierarchical namespace. The
    202      * {@code Logger.addLogger()} factory methods call this method to add newly
    203      * created Logger. This returns false if a logger with the given name has
    204      * existed in the namespace
    205      * <p>
    206      * Note that the {@code LogManager} may only retain weak references to
    207      * registered loggers. In order to prevent {@code Logger} objects from being
    208      * unexpectedly garbage collected it is necessary for <i>applications</i>
    209      * to maintain references to them.
    210      * </p>
    211      *
    212      * @param logger
    213      *            the logger to be added.
    214      * @return true if the given logger is added into the namespace
    215      *         successfully, false if the given logger exists in the namespace.
    216      */
    217     public synchronized boolean addLogger(Logger logger) {
    218         String name = logger.getName();
    219         if (loggers.get(name) != null) {
    220             return false;
    221         }
    222         addToFamilyTree(logger, name);
    223         loggers.put(name, logger);
    224         logger.setManager(this);
    225         return true;
    226     }
    227 
    228     private void addToFamilyTree(Logger logger, String name) {
    229         Logger parent = null;
    230         // find parent
    231         int lastSeparator;
    232         String parentName = name;
    233         while ((lastSeparator = parentName.lastIndexOf('.')) != -1) {
    234             parentName = parentName.substring(0, lastSeparator);
    235             parent = loggers.get(parentName);
    236             if (parent != null) {
    237                 setParent(logger, parent);
    238                 break;
    239             } else if (getProperty(parentName + ".level") != null ||
    240                     getProperty(parentName + ".handlers") != null) {
    241                 parent = Logger.getLogger(parentName);
    242                 setParent(logger, parent);
    243                 break;
    244             }
    245         }
    246         if (parent == null && (parent = loggers.get("")) != null) {
    247             setParent(logger, parent);
    248         }
    249 
    250         // find children
    251         // TODO: performance can be improved here?
    252         String nameDot = name + '.';
    253         Collection<Logger> allLoggers = loggers.values();
    254         for (final Logger child : allLoggers) {
    255             Logger oldParent = child.getParent();
    256             if (parent == oldParent && (name.length() == 0 || child.getName().startsWith(nameDot))) {
    257                 final Logger thisLogger = logger;
    258                 child.setParent(thisLogger);
    259                 if (oldParent != null) {
    260                     // -- remove from old parent as the parent has been changed
    261                     oldParent.children.remove(child);
    262                 }
    263             }
    264         }
    265     }
    266 
    267     /**
    268      * Get the logger with the given name.
    269      *
    270      * @param name
    271      *            name of logger
    272      * @return logger with given name, or {@code null} if nothing is found.
    273      */
    274     public synchronized Logger getLogger(String name) {
    275         return loggers.get(name);
    276     }
    277 
    278     /**
    279      * Get a {@code Enumeration} of all registered logger names.
    280      *
    281      * @return enumeration of registered logger names
    282      */
    283     public synchronized Enumeration<String> getLoggerNames() {
    284         return loggers.keys();
    285     }
    286 
    287     /**
    288      * Get the global {@code LogManager} instance.
    289      *
    290      * @return the global {@code LogManager} instance
    291      */
    292     public static LogManager getLogManager() {
    293         return manager;
    294     }
    295 
    296     /**
    297      * Get the value of property with given name.
    298      *
    299      * @param name
    300      *            the name of property
    301      * @return the value of property
    302      */
    303     public String getProperty(String name) {
    304         return props.getProperty(name);
    305     }
    306 
    307     /**
    308      * Re-initialize the properties and configuration. The initialization
    309      * process is same as the {@code LogManager} instantiation.
    310      * <p>
    311      * Notice : No {@code PropertyChangeEvent} are fired.
    312      * </p>
    313      *
    314      * @throws IOException
    315      *             if any IO related problems happened.
    316      */
    317     public void readConfiguration() throws IOException {
    318         // check config class
    319         String configClassName = System.getProperty("java.util.logging.config.class");
    320         if (configClassName == null || getInstanceByClass(configClassName) == null) {
    321             // if config class failed, check config file
    322             String configFile = System.getProperty("java.util.logging.config.file");
    323 
    324             if (configFile == null) {
    325                 // if cannot find configFile, use default logging.properties
    326                 configFile = System.getProperty("java.home") + File.separator + "lib" +
    327                         File.separator + "logging.properties";
    328             }
    329 
    330             InputStream input = null;
    331             try {
    332                 try {
    333                     input = new FileInputStream(configFile);
    334                 } catch (IOException exception) {
    335                     // fall back to using the built-in logging.properties file
    336                     input = LogManager.class.getResourceAsStream("logging.properties");
    337                     if (input == null) {
    338                         throw exception;
    339                     }
    340                 }
    341                 readConfiguration(new BufferedInputStream(input));
    342             } finally {
    343                 IoUtils.closeQuietly(input);
    344             }
    345         }
    346     }
    347 
    348     // use SystemClassLoader to load class from system classpath
    349     static Object getInstanceByClass(final String className) {
    350         try {
    351             Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass(className);
    352             return clazz.newInstance();
    353         } catch (Exception e) {
    354             try {
    355                 Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
    356                 return clazz.newInstance();
    357             } catch (Exception innerE) {
    358                 System.err.println("Loading class '" + className + "' failed");
    359                 System.err.println(innerE);
    360                 return null;
    361             }
    362         }
    363     }
    364 
    365     // actual initialization process from a given input stream
    366     private synchronized void readConfigurationImpl(InputStream ins)
    367             throws IOException {
    368         reset();
    369         props.load(ins);
    370 
    371         // The RI treats the root logger as special. For compatibility, always
    372         // update the root logger's handlers.
    373         Logger root = loggers.get("");
    374         if (root != null) {
    375             root.setManager(this);
    376         }
    377 
    378         // parse property "config" and apply setting
    379         String configs = props.getProperty("config");
    380         if (configs != null) {
    381             StringTokenizer st = new StringTokenizer(configs, " ");
    382             while (st.hasMoreTokens()) {
    383                 String configerName = st.nextToken();
    384                 getInstanceByClass(configerName);
    385             }
    386         }
    387 
    388         // set levels for logger
    389         Collection<Logger> allLoggers = loggers.values();
    390         for (Logger logger : allLoggers) {
    391             String property = props.getProperty(logger.getName() + ".level");
    392             if (property != null) {
    393                 logger.setLevel(Level.parse(property));
    394             }
    395         }
    396         listeners.firePropertyChange(null, null, null);
    397     }
    398 
    399     /**
    400      * Re-initialize the properties and configuration from the given
    401      * {@code InputStream}
    402      * <p>
    403      * Notice : No {@code PropertyChangeEvent} are fired.
    404      * </p>
    405      *
    406      * @param ins
    407      *            the input stream
    408      * @throws IOException
    409      *             if any IO related problems happened.
    410      */
    411     public void readConfiguration(InputStream ins) throws IOException {
    412         checkAccess();
    413         readConfigurationImpl(ins);
    414     }
    415 
    416     /**
    417      * Reset configuration.
    418      *
    419      * <p>All handlers are closed and removed from any named loggers. All loggers'
    420      * level is set to null, except the root logger's level is set to
    421      * {@code Level.INFO}.
    422      */
    423     public synchronized void reset() {
    424         checkAccess();
    425         props = new Properties();
    426         Enumeration<String> names = getLoggerNames();
    427         while (names.hasMoreElements()) {
    428             String name = names.nextElement();
    429             Logger logger = getLogger(name);
    430             if (logger != null) {
    431                 logger.reset();
    432             }
    433         }
    434         Logger root = loggers.get("");
    435         if (root != null) {
    436             root.setLevel(Level.INFO);
    437         }
    438     }
    439 
    440     /**
    441      * Add a {@code PropertyChangeListener}, which will be invoked when
    442      * the properties are reread.
    443      *
    444      * @param l
    445      *            the {@code PropertyChangeListener} to be added.
    446      */
    447     public void addPropertyChangeListener(PropertyChangeListener l) {
    448         if (l == null) {
    449             throw new NullPointerException("l == null");
    450         }
    451         checkAccess();
    452         listeners.addPropertyChangeListener(l);
    453     }
    454 
    455     /**
    456      * Remove a {@code PropertyChangeListener}, do nothing if the given
    457      * listener is not found.
    458      *
    459      * @param l
    460      *            the {@code PropertyChangeListener} to be removed.
    461      */
    462     public void removePropertyChangeListener(PropertyChangeListener l) {
    463         checkAccess();
    464         listeners.removePropertyChangeListener(l);
    465     }
    466 
    467     /**
    468      * Returns a named logger associated with the supplied resource bundle.
    469      *
    470      * @param resourceBundleName the resource bundle to associate, or null for
    471      *      no associated resource bundle.
    472      */
    473     synchronized Logger getOrCreate(String name, String resourceBundleName) {
    474         Logger result = getLogger(name);
    475         if (result == null) {
    476             result = new Logger(name, resourceBundleName);
    477             addLogger(result);
    478         }
    479         return result;
    480     }
    481 
    482 
    483     /**
    484      * Sets the parent of this logger in the namespace. Callers must first
    485      * {@link #checkAccess() check security}.
    486      *
    487      * @param newParent
    488      *            the parent logger to set.
    489      */
    490     synchronized void setParent(Logger logger, Logger newParent) {
    491         logger.parent = newParent;
    492 
    493         if (logger.levelObjVal == null) {
    494             setLevelRecursively(logger, null);
    495         }
    496         newParent.children.add(logger);
    497         logger.updateDalvikLogHandler();
    498     }
    499 
    500     /**
    501      * Sets the level on {@code logger} to {@code newLevel}. Any child loggers
    502      * currently inheriting their level from {@code logger} will be updated
    503      * recursively.
    504      *
    505      * @param newLevel the new minimum logging threshold. If null, the logger's
    506      *      parent level will be used; or {@code Level.INFO} for loggers with no
    507      *      parent.
    508      */
    509     synchronized void setLevelRecursively(Logger logger, Level newLevel) {
    510         int previous = logger.levelIntVal;
    511         logger.levelObjVal = newLevel;
    512 
    513         if (newLevel == null) {
    514             logger.levelIntVal = logger.parent != null
    515                     ? logger.parent.levelIntVal
    516                     : Level.INFO.intValue();
    517         } else {
    518             logger.levelIntVal = newLevel.intValue();
    519         }
    520 
    521         if (previous != logger.levelIntVal) {
    522             for (Logger child : logger.children) {
    523                 if (child.levelObjVal == null) {
    524                     setLevelRecursively(child, null);
    525                 }
    526             }
    527         }
    528     }
    529 }
    530