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
18package java.util.logging;
19
20// BEGIN android-note
21// this file contains cleaned up documentation and style for contribution
22// upstream.
23// javax.management support (MBeans) has been dropped.
24// END android-note
25
26import org.apache.harmony.logging.internal.nls.Messages;
27
28import java.beans.PropertyChangeListener;
29import java.beans.PropertyChangeSupport;
30import java.io.BufferedInputStream;
31import java.io.File;
32import java.io.FileInputStream;
33import java.io.IOException;
34import java.io.InputStream;
35import java.security.AccessController;
36import java.security.PrivilegedAction;
37import java.util.Collection;
38import java.util.Enumeration;
39import java.util.Hashtable;
40import java.util.Properties;
41import java.util.StringTokenizer;
42
43/**
44 * {@code LogManager} is used to maintain configuration properties of the
45 * logging framework, and to manage a hierarchical namespace of all named
46 * {@code Logger} objects.
47 * <p>
48 * There is only one global {@code LogManager} instance in the
49 * application, which can be get by calling static method
50 * {@link #getLogManager()}. This instance is created and
51 * initialized during class initialization and cannot be changed.
52 * <p>
53 * The {@code LogManager} class can be specified by
54 * java.util.logging.manager system property, if the property is unavailable or
55 * invalid, the default class {@link java.util.logging.LogManager} will
56 * be used.
57 * <p>
58 * On initialization, {@code LogManager} reads its configuration from a
59 * properties file, which by default is the "lib/logging.properties" in the JRE
60 * directory.
61 * <p>
62 * However, two optional system properties can be used to customize the initial
63 * configuration process of {@code LogManager}.
64 * <ul>
65 * <li>"java.util.logging.config.class"</li>
66 * <li>"java.util.logging.config.file"</li>
67 * </ul>
68 * <p>
69 * These two properties can be set in three ways, by the Preferences API, by the
70 * "java" command line property definitions, or by system property definitions
71 * passed to JNI_CreateJavaVM.
72 * <p>
73 * The "java.util.logging.config.class" should specifies a class name. If it is
74 * set, this given class will be loaded and instantiated during
75 * {@code LogManager} initialization, so that this object's default
76 * constructor can read the initial configuration and define properties for
77 * {@code LogManager}.
78 * <p>
79 * If "java.util.logging.config.class" property is not set, or it is invalid, or
80 * some exception is thrown during the instantiation, then the
81 * "java.util.logging.config.file" system property can be used to specify a
82 * properties file. The {@code LogManager} will read initial
83 * configuration from this file.
84 * <p>
85 * If neither of these properties is defined, or some exception is thrown
86 * during these two properties using, the {@code LogManager} will read
87 * its initial configuration from default properties file, as described above.
88 * <p>
89 * The global logging properties may include:
90 * <ul>
91 * <li>"handlers". This property's values should be a list of class names for
92 * handler classes separated by whitespace, these classes must be subclasses of
93 * {@code Handler} and each must have a default constructor, these
94 * classes will be loaded, instantiated and registered as handlers on the root
95 * {@code Logger} (the {@code Logger} named ""). These
96 * {@code Handler}s maybe initialized lazily.</li>
97 * <li>"config". The property defines a list of class names separated by
98 * whitespace. Each class must have a default constructor, in which it can
99 * update the logging configuration, such as levels, handlers, or filters for
100 * some logger, etc. These classes will be loaded and instantiated during
101 * {@code LogManager} configuration</li>
102 * </ul>
103 * <p>
104 * This class, together with any handler and configuration classes associated
105 * with it, <b>must</b> be loaded from the system classpath when
106 * {@code LogManager} configuration occurs.
107 * <p>
108 * Besides global properties, the properties for loggers and Handlers can be
109 * specified in the property files. The names of these properties will start
110 * with the complete dot separated names for the handlers or loggers.
111 * <p>
112 * In the {@code LogManager}'s hierarchical namespace,
113 * {@code Loggers} are organized based on their dot separated names. For
114 * example, "x.y.z" is child of "x.y".
115 * <p>
116 * Levels for {@code Loggers} can be defined by properties whose name end
117 * with ".level". Thus "alogger.level" defines a level for the logger named as
118 * "alogger" and for all its children in the naming hierarchy. Log levels
119 * properties are read and applied in the same order as they are specified in
120 * the property file. The root logger's level can be defined by the property
121 * named as ".level".
122 * <p>
123 * This class is thread safe. It is an error to synchronize on a
124 * {@code LogManager} while synchronized on a {@code Logger}.
125 */
126public class LogManager {
127
128    /** The line separator of the underlying OS. */
129    private static final String lineSeparator = getPrivilegedSystemProperty("line.separator"); //$NON-NLS-1$
130
131    /** The shared logging permission. */
132    private static final LoggingPermission perm = new LoggingPermission(
133            "control", null); //$NON-NLS-1$
134
135    /** The singleton instance. */
136    static LogManager manager;
137
138    /**
139     * The {@code String} value of the {@link LoggingMXBean}'s ObjectName.
140     */
141    public static final String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; //$NON-NLS-1$
142
143    /**
144     * Get the {@code LoggingMXBean} instance. this implementation always throws
145     * an UnsupportedOperationException.
146     *
147     * @return the {@code LoggingMXBean} instance
148     */
149    public static LoggingMXBean getLoggingMXBean() {
150      // BEGIN android-added
151      throw new UnsupportedOperationException();
152      // END android-added
153      // BEGIN android-removed
154      // try {
155      //     ObjectName loggingMXBeanName = new ObjectName(LOGGING_MXBEAN_NAME);
156      //     MBeanServer platformBeanServer = ManagementFactory
157      //             .getPlatformMBeanServer();
158      //     Set<?> loggingMXBeanSet = platformBeanServer.queryMBeans(
159      //             loggingMXBeanName, null);
160      //
161      //     if (loggingMXBeanSet.size() != 1) {
162      //         // logging.21=There Can Be Only One logging MX bean.
163      //         throw new AssertionError(Messages.getString("logging.21")); //$NON-NLS-1$
164      //     }
165      //
166      //     Iterator<?> i = loggingMXBeanSet.iterator();
167      //     ObjectInstance loggingMXBeanOI = (ObjectInstance) i.next();
168      //     String lmxbcn = loggingMXBeanOI.getClassName();
169      //     Class<?> lmxbc = Class.forName(lmxbcn);
170      //     Method giMethod = lmxbc.getDeclaredMethod("getInstance"); //$NON-NLS-1$
171      //     giMethod.setAccessible(true);
172      //     LoggingMXBean lmxb = (LoggingMXBean) giMethod.invoke(null,
173      //             new Object[] {});
174      //
175      //     return lmxb;
176      // } catch (Exception e) {
177      //     // TODO
178      //     // e.printStackTrace();
179      // }
180      // // logging.22=Exception occurred while getting the logging MX bean.
181      // throw new AssertionError(Messages.getString("logging.22")); //$NON-NLS-1$
182      // END android-removed
183    }
184
185    // FIXME: use weak reference to avoid heap memory leak
186    private Hashtable<String, Logger> loggers;
187
188    /** The configuration properties */
189    private Properties props;
190
191    /** the property change listener */
192    private PropertyChangeSupport listeners;
193
194    static {
195        // init LogManager singleton instance
196        AccessController.doPrivileged(new PrivilegedAction<Object>() {
197            public Object run() {
198                String className = System
199                        .getProperty("java.util.logging.manager"); //$NON-NLS-1$
200
201                if (null != className) {
202                    manager = (LogManager) getInstanceByClass(className);
203                }
204                if (null == manager) {
205                    manager = new LogManager();
206                }
207
208                // read configuration
209                try {
210                    manager.readConfiguration();
211                } catch (Exception e) {
212                    e.printStackTrace();
213                }
214
215                // if global logger has been initialized, set root as its parent
216                Logger root = new Logger("", null); //$NON-NLS-1$
217                root.setLevel(Level.INFO);
218                Logger.global.setParent(root);
219
220                manager.addLogger(root);
221                manager.addLogger(Logger.global);
222                return null;
223            }
224        });
225    }
226
227    /**
228     * Default constructor. This is not public because there should be only one
229     * {@code LogManager} instance, which can be get by
230     * {@code LogManager.getLogManager()}. This is protected so that
231     * application can subclass the object.
232     */
233    protected LogManager() {
234        loggers = new Hashtable<String, Logger>();
235        props = new Properties();
236        listeners = new PropertyChangeSupport(this);
237        // add shutdown hook to ensure that the associated resource will be
238        // freed when JVM exits
239        AccessController.doPrivileged(new PrivilegedAction<Void>() {
240            public Void run() {
241                Runtime.getRuntime().addShutdownHook(new Thread() {
242                    @Override
243                    public void run() {
244                        reset();
245                    }
246                });
247                return null;
248            }
249        });
250    }
251
252    /*
253     * Package private utilities Returns the line separator of the underlying
254     * OS.
255     */
256    static String getSystemLineSeparator() {
257        return lineSeparator;
258    }
259
260    /**
261     * Check that the caller has {@code LoggingPermission("control")} so
262     * that it is trusted to modify the configuration for logging framework. If
263     * the check passes, just return, otherwise {@code SecurityException}
264     * will be thrown.
265     *
266     * @throws SecurityException
267     *             if there is a security manager in operation and the invoker
268     *             of this method does not have the required security permission
269     *             {@code LoggingPermission("control")}
270     */
271    public void checkAccess() {
272        if (null != System.getSecurityManager()) {
273            System.getSecurityManager().checkPermission(perm);
274        }
275    }
276
277    /**
278     * Add a given logger into the hierarchical namespace. The
279     * {@code Logger.addLogger()} factory methods call this method to add newly
280     * created Logger. This returns false if a logger with the given name has
281     * existed in the namespace
282     * <p>
283     * Note that the {@code LogManager} may only retain weak references to
284     * registered loggers. In order to prevent {@code Logger} objects from being
285     * unexpectedly garbage collected it is necessary for <i>applications</i>
286     * to maintain references to them.
287     * </p>
288     *
289     * @param logger
290     *            the logger to be added.
291     * @return true if the given logger is added into the namespace
292     *         successfully, false if the given logger exists in the namespace.
293     */
294    public synchronized boolean addLogger(Logger logger) {
295        String name = logger.getName();
296        if (null != loggers.get(name)) {
297            return false;
298        }
299        addToFamilyTree(logger, name);
300        loggers.put(name, logger);
301        logger.setManager(this);
302        return true;
303    }
304
305    private void addToFamilyTree(Logger logger, String name) {
306        Logger parent = null;
307        // find parent
308        int lastSeparator;
309        String parentName = name;
310        while ((lastSeparator = parentName.lastIndexOf('.')) != -1) {
311            parentName = parentName.substring(0, lastSeparator);
312            parent = loggers.get(parentName);
313            if (parent != null) {
314                setParent(logger, parent);
315                break;
316            } else if (getProperty(parentName + ".level") != null || //$NON-NLS-1$
317                    getProperty(parentName + ".handlers") != null) { //$NON-NLS-1$
318                parent = Logger.getLogger(parentName);
319                setParent(logger, parent);
320                break;
321            }
322        }
323        if (parent == null && null != (parent = loggers.get(""))) { //$NON-NLS-1$
324            setParent(logger, parent);
325        }
326
327        // find children
328        // TODO: performance can be improved here?
329        String nameDot = name + '.';
330        Collection<Logger> allLoggers = loggers.values();
331        for (final Logger child : allLoggers) {
332            Logger oldParent = child.getParent();
333            if (parent == oldParent
334                    && (name.length() == 0 || child.getName().startsWith(
335                            nameDot))) {
336                final Logger thisLogger = logger;
337                AccessController.doPrivileged(new PrivilegedAction<Object>() {
338                    public Object run() {
339                        child.setParent(thisLogger);
340                        return null;
341                    }
342                });
343                if (null != oldParent) {
344                    // -- remove from old parent as the parent has been changed
345                    oldParent.children.remove(child);
346                }
347            }
348        }
349    }
350
351    /**
352     * Get the logger with the given name.
353     *
354     * @param name
355     *            name of logger
356     * @return logger with given name, or {@code null} if nothing is found.
357     */
358    public synchronized Logger getLogger(String name) {
359        return loggers.get(name);
360    }
361
362    /**
363     * Get a {@code Enumeration} of all registered logger names.
364     *
365     * @return enumeration of registered logger names
366     */
367    public synchronized Enumeration<String> getLoggerNames() {
368        return loggers.keys();
369    }
370
371    /**
372     * Get the global {@code LogManager} instance.
373     *
374     * @return the global {@code LogManager} instance
375     */
376    public static LogManager getLogManager() {
377        return manager;
378    }
379
380    /**
381     * Get the value of property with given name.
382     *
383     * @param name
384     *            the name of property
385     * @return the value of property
386     */
387    public String getProperty(String name) {
388        return props.getProperty(name);
389    }
390
391    /**
392     * Re-initialize the properties and configuration. The initialization
393     * process is same as the {@code LogManager} instantiation.
394     * <p>
395     * Notice : No {@code PropertyChangeEvent} are fired.
396     * </p>
397     *
398     * @throws IOException
399     *             if any IO related problems happened.
400     * @throws SecurityException
401     *             if security manager exists and it determines that caller does
402     *             not have the required permissions to perform this action.
403     */
404    public void readConfiguration() throws IOException {
405        // check config class
406        String configClassName = System
407                .getProperty("java.util.logging.config.class"); //$NON-NLS-1$
408        if (null == configClassName
409                || null == getInstanceByClass(configClassName)) {
410            // if config class failed, check config file
411            String configFile = System
412                    .getProperty("java.util.logging.config.file"); //$NON-NLS-1$
413
414            if (null == configFile) {
415                // if cannot find configFile, use default logging.properties
416                configFile = new StringBuilder().append(
417                        System.getProperty("java.home")).append(File.separator) //$NON-NLS-1$
418                        .append("lib").append(File.separator).append( //$NON-NLS-1$
419                                "logging.properties").toString(); //$NON-NLS-1$
420            }
421
422            InputStream input = null;
423            try {
424                // BEGIN android-removed
425                // input = new BufferedInputStream(new FileInputStream(configFile));
426                // END android-removed
427
428                // BEGIN android-added
429                try {
430                    input = new BufferedInputStream(
431                            new FileInputStream(configFile), 8192);
432                } catch (Exception ex) {
433                    // consult fixed resource as a last resort
434                    input = new BufferedInputStream(
435                            getClass().getResourceAsStream(
436                                    "logging.properties"), 8192);
437                }
438                // END android-added
439                readConfiguration(input);
440            } finally {
441                if (input != null) {
442                    try {
443                        input.close();
444                    } catch (Exception e) {// ignore
445                    }
446                }
447            }
448        }
449    }
450
451    // use privilege code to get system property
452    static String getPrivilegedSystemProperty(final String key) {
453        return AccessController.doPrivileged(new PrivilegedAction<String>() {
454            public String run() {
455                return System.getProperty(key);
456            }
457        });
458    }
459
460    // use SystemClassLoader to load class from system classpath
461    static Object getInstanceByClass(final String className) {
462        try {
463            Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass(
464                    className);
465            return clazz.newInstance();
466        } catch (Exception e) {
467            try {
468                Class<?> clazz = Thread.currentThread().getContextClassLoader()
469                        .loadClass(className);
470                return clazz.newInstance();
471            } catch (Exception innerE) {
472                // logging.20=Loading class "{0}" failed
473                System.err.println(Messages.getString("logging.20", className)); //$NON-NLS-1$
474                System.err.println(innerE);
475                return null;
476            }
477        }
478
479    }
480
481    // actual initialization process from a given input stream
482    private synchronized void readConfigurationImpl(InputStream ins)
483            throws IOException {
484        reset();
485        props.load(ins);
486
487        // The RI treats the root logger as special. For compatibility, always
488        // update the root logger's handlers.
489        Logger root = loggers.get("");
490        if (root != null) {
491            root.setManager(this);
492        }
493
494        // parse property "config" and apply setting
495        String configs = props.getProperty("config"); //$NON-NLS-1$
496        if (null != configs) {
497            StringTokenizer st = new StringTokenizer(configs, " "); //$NON-NLS-1$
498            while (st.hasMoreTokens()) {
499                String configerName = st.nextToken();
500                getInstanceByClass(configerName);
501            }
502        }
503
504        // set levels for logger
505        Collection<Logger> allLoggers = loggers.values();
506        for (Logger logger : allLoggers) {
507            String property = props.getProperty(logger.getName() + ".level"); //$NON-NLS-1$
508            if (null != property) {
509                logger.setLevel(Level.parse(property));
510            }
511        }
512        listeners.firePropertyChange(null, null, null);
513    }
514
515    /**
516     * Re-initialize the properties and configuration from the given
517     * {@code InputStream}
518     * <p>
519     * Notice : No {@code PropertyChangeEvent} are fired.
520     * </p>
521     *
522     * @param ins
523     *            the input stream
524     * @throws IOException
525     *             if any IO related problems happened.
526     * @throws SecurityException
527     *             if security manager exists and it determines that caller does
528     *             not have the required permissions to perform this action.
529     */
530    public void readConfiguration(InputStream ins) throws IOException {
531        checkAccess();
532        readConfigurationImpl(ins);
533    }
534
535    /**
536     * Reset configuration.
537     * <p>
538     * All handlers are closed and removed from any named loggers. All loggers'
539     * level is set to null, except the root logger's level is set to
540     * {@code Level.INFO}.
541     * </p>
542     *
543     * @throws SecurityException
544     *             if security manager exists and it determines that caller does
545     *             not have the required permissions to perform this action.
546     */
547    public synchronized void reset() {
548        checkAccess();
549        props = new Properties();
550        Enumeration<String> names = getLoggerNames();
551        while (names.hasMoreElements()) {
552            String name = names.nextElement();
553            Logger logger = getLogger(name);
554            if (logger != null) {
555                logger.reset();
556            }
557        }
558        Logger root = loggers.get(""); //$NON-NLS-1$
559        if (null != root) {
560            root.setLevel(Level.INFO);
561        }
562    }
563
564    /**
565     * Add a {@code PropertyChangeListener}, which will be invoked when
566     * the properties are reread.
567     *
568     * @param l
569     *            the {@code PropertyChangeListener} to be added.
570     * @throws SecurityException
571     *             if security manager exists and it determines that caller does
572     *             not have the required permissions to perform this action.
573     */
574    public void addPropertyChangeListener(PropertyChangeListener l) {
575        if (l == null) {
576            throw new NullPointerException();
577        }
578        checkAccess();
579        listeners.addPropertyChangeListener(l);
580    }
581
582    /**
583     * Remove a {@code PropertyChangeListener}, do nothing if the given
584     * listener is not found.
585     *
586     * @param l
587     *            the {@code PropertyChangeListener} to be removed.
588     * @throws SecurityException
589     *             if security manager exists and it determines that caller does
590     *             not have the required permissions to perform this action.
591     */
592    public void removePropertyChangeListener(PropertyChangeListener l) {
593        checkAccess();
594        listeners.removePropertyChangeListener(l);
595    }
596
597    /**
598     * Returns a named logger associated with the supplied resource bundle.
599     *
600     * @param resourceBundleName the resource bundle to associate, or null for
601     *      no associated resource bundle.
602     */
603    synchronized Logger getOrCreate(String name, String resourceBundleName) {
604        Logger result = getLogger(name);
605        if (result == null) {
606            result = new Logger(name, resourceBundleName);
607            addLogger(result);
608        }
609        return result;
610    }
611
612
613    /**
614     * Sets the parent of this logger in the namespace. Callers must first
615     * {@link #checkAccess() check security}.
616     *
617     * @param newParent
618     *            the parent logger to set.
619     */
620    synchronized void setParent(Logger logger, Logger newParent) {
621        logger.parent = newParent;
622
623        if (logger.levelObjVal == null) {
624            setLevelRecursively(logger, null);
625        }
626        newParent.children.add(logger);
627        logger.updateDalvikLogHandler(); // android-only
628    }
629
630    /**
631     * Sets the level on {@code logger} to {@code newLevel}. Any child loggers
632     * currently inheriting their level from {@code logger} will be updated
633     * recursively.
634     *
635     * @param newLevel the new minimum logging threshold. If null, the logger's
636     *      parent level will be used; or {@code Level.INFO} for loggers with no
637     *      parent.
638     */
639    synchronized void setLevelRecursively(Logger logger, Level newLevel) {
640        int previous = logger.levelIntVal;
641        logger.levelObjVal = newLevel;
642
643        if (newLevel == null) {
644            logger.levelIntVal = logger.parent != null
645                    ? logger.parent.levelIntVal
646                    : Level.INFO.intValue();
647        } else {
648            logger.levelIntVal = newLevel.intValue();
649        }
650
651        if (previous != logger.levelIntVal) {
652            for (Logger child : logger.children) {
653                if (child.levelObjVal == null) {
654                    setLevelRecursively(child, null);
655                }
656            }
657        }
658    }
659}
660