| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| |
| package java.util.logging; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.security.*; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.beans.PropertyChangeListener; |
| |
| /** |
| * There is a single global LogManager object that is used to |
| * maintain a set of shared state about Loggers and log services. |
| * <p> |
| * This LogManager object: |
| * <ul> |
| * <li> Manages a hierarchical namespace of Logger objects. All |
| * named Loggers are stored in this namespace. |
| * <li> Manages a set of logging control properties. These are |
| * simple key-value pairs that can be used by Handlers and |
| * other logging objects to configure themselves. |
| * </ul> |
| * <p> |
| * The global LogManager object can be retrieved using LogManager.getLogManager(). |
| * The LogManager object is created during class initialization and |
| * cannot subsequently be changed. |
| * <p> |
| * At startup the LogManager class is located using the |
| * java.util.logging.manager system property. |
| * <p> |
| * The LogManager defines two optional system properties that allow control over |
| * the initial configuration: |
| * <ul> |
| * <li>"java.util.logging.config.class" |
| * <li>"java.util.logging.config.file" |
| * </ul> |
| * These two properties may be specified on the command line to the "java" |
| * command, or as system property definitions passed to JNI_CreateJavaVM. |
| * <p> |
| * If the "java.util.logging.config.class" property is set, then the |
| * property value is treated as a class name. The given class will be |
| * loaded, an object will be instantiated, and that object's constructor |
| * is responsible for reading in the initial configuration. (That object |
| * may use other system properties to control its configuration.) The |
| * alternate configuration class can use <tt>readConfiguration(InputStream)</tt> |
| * to define properties in the LogManager. |
| * <p> |
| * If "java.util.logging.config.class" property is <b>not</b> set, |
| * then the "java.util.logging.config.file" system property can be used |
| * to specify a properties file (in java.util.Properties format). The |
| * initial logging configuration will be read from this file. |
| * <p> |
| * If neither of these properties is defined then the LogManager uses its |
| * default configuration. The default configuration is typically loaded from the |
| * properties file "{@code lib/logging.properties}" in the Java installation |
| * directory. |
| * <p> |
| * The properties for loggers and Handlers will have names starting |
| * with the dot-separated name for the handler or logger. |
| * <p> |
| * The global logging properties may include: |
| * <ul> |
| * <li>A property "handlers". This defines a whitespace or comma separated |
| * list of class names for handler classes to load and register as |
| * handlers on the root Logger (the Logger named ""). Each class |
| * name must be for a Handler class which has a default constructor. |
| * Note that these Handlers may be created lazily, when they are |
| * first used. |
| * |
| * <li>A property "<logger>.handlers". This defines a whitespace or |
| * comma separated list of class names for handlers classes to |
| * load and register as handlers to the specified logger. Each class |
| * name must be for a Handler class which has a default constructor. |
| * Note that these Handlers may be created lazily, when they are |
| * first used. |
| * |
| * <li>A property "<logger>.useParentHandlers". This defines a boolean |
| * value. By default every logger calls its parent in addition to |
| * handling the logging message itself, this often result in messages |
| * being handled by the root logger as well. When setting this property |
| * to false a Handler needs to be configured for this logger otherwise |
| * no logging messages are delivered. |
| * |
| * <li>A property "config". This property is intended to allow |
| * arbitrary configuration code to be run. The property defines a |
| * whitespace or comma separated list of class names. A new instance will be |
| * created for each named class. The default constructor of each class |
| * may execute arbitrary code to update the logging configuration, such as |
| * setting logger levels, adding handlers, adding filters, etc. |
| * </ul> |
| * <p> |
| * Note that all classes loaded during LogManager configuration are |
| * first searched on the system class path before any user class path. |
| * That includes the LogManager class, any config classes, and any |
| * handler classes. |
| * <p> |
| * Loggers are organized into a naming hierarchy based on their |
| * dot separated names. Thus "a.b.c" is a child of "a.b", but |
| * "a.b1" and a.b2" are peers. |
| * <p> |
| * All properties whose names end with ".level" are assumed to define |
| * log levels for Loggers. Thus "foo.level" defines a log level for |
| * the logger called "foo" and (recursively) for any of its children |
| * in the naming hierarchy. Log Levels are applied in the order they |
| * are defined in the properties file. Thus level settings for child |
| * nodes in the tree should come after settings for their parents. |
| * The property name ".level" can be used to set the level for the |
| * root of the tree. |
| * <p> |
| * All methods on the LogManager object are multi-thread safe. |
| * |
| * @since 1.4 |
| */ |
| |
| public class LogManager { |
| // The global LogManager object |
| private static final LogManager manager; |
| |
| // 'props' is assigned within a lock but accessed without it. |
| // Declaring it volatile makes sure that another thread will not |
| // be able to see a partially constructed 'props' object. |
| // (seeing a partially constructed 'props' object can result in |
| // NPE being thrown in Hashtable.get(), because it leaves the door |
| // open for props.getProperties() to be called before the construcor |
| // of Hashtable is actually completed). |
| private volatile Properties props = new Properties(); |
| private final static Level defaultLevel = Level.INFO; |
| |
| // The map of the registered listeners. The map value is the registration |
| // count to allow for cases where the same listener is registered many times. |
| private final Map<Object,Integer> listenerMap = new HashMap<>(); |
| |
| // LoggerContext for system loggers and user loggers |
| private final LoggerContext systemContext = new SystemLoggerContext(); |
| private final LoggerContext userContext = new LoggerContext(); |
| // non final field - make it volatile to make sure that other threads |
| // will see the new value once ensureLogManagerInitialized() has finished |
| // executing. |
| private volatile Logger rootLogger; |
| // Have we done the primordial reading of the configuration file? |
| // (Must be done after a suitable amount of java.lang.System |
| // initialization has been done) |
| private volatile boolean readPrimordialConfiguration; |
| // Have we initialized global (root) handlers yet? |
| // This gets set to false in readConfiguration |
| private boolean initializedGlobalHandlers = true; |
| // True if JVM death is imminent and the exit hook has been called. |
| private boolean deathImminent; |
| |
| static { |
| manager = AccessController.doPrivileged(new PrivilegedAction<LogManager>() { |
| @Override |
| public LogManager run() { |
| LogManager mgr = null; |
| String cname = null; |
| try { |
| cname = System.getProperty("java.util.logging.manager"); |
| if (cname != null) { |
| // Android-changed: Extract logic into the getClassInstance() helper. |
| mgr = (LogManager) getClassInstance(cname).newInstance(); |
| } |
| } catch (Exception ex) { |
| System.err.println("Could not load Logmanager \"" + cname + "\""); |
| ex.printStackTrace(); |
| } |
| if (mgr == null) { |
| mgr = new LogManager(); |
| } |
| return mgr; |
| |
| } |
| }); |
| } |
| |
| |
| // This private class is used as a shutdown hook. |
| // It does a "reset" to close all open handlers. |
| private class Cleaner extends Thread { |
| |
| private Cleaner() { |
| /* Set context class loader to null in order to avoid |
| * keeping a strong reference to an application classloader. |
| */ |
| this.setContextClassLoader(null); |
| } |
| |
| @Override |
| public void run() { |
| // This is to ensure the LogManager.<clinit> is completed |
| // before synchronized block. Otherwise deadlocks are possible. |
| LogManager mgr = manager; |
| |
| // If the global handlers haven't been initialized yet, we |
| // don't want to initialize them just so we can close them! |
| synchronized (LogManager.this) { |
| // Note that death is imminent. |
| deathImminent = true; |
| initializedGlobalHandlers = true; |
| } |
| |
| // Do a reset to close all active handlers. |
| reset(); |
| } |
| } |
| |
| |
| /** |
| * Protected constructor. This is protected so that container applications |
| * (such as J2EE containers) can subclass the object. It is non-public as |
| * it is intended that there only be one LogManager object, whose value is |
| * retrieved by calling LogManager.getLogManager. |
| */ |
| protected LogManager() { |
| this(checkSubclassPermissions()); |
| } |
| |
| private LogManager(Void checked) { |
| |
| // Add a shutdown hook to close the global handlers. |
| try { |
| Runtime.getRuntime().addShutdownHook(new Cleaner()); |
| } catch (IllegalStateException e) { |
| // If the VM is already shutting down, |
| // We do not need to register shutdownHook. |
| } |
| } |
| |
| private static Void checkSubclassPermissions() { |
| final SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) { |
| // These permission will be checked in the LogManager constructor, |
| // in order to register the Cleaner() thread as a shutdown hook. |
| // Check them here to avoid the penalty of constructing the object |
| // etc... |
| sm.checkPermission(new RuntimePermission("shutdownHooks")); |
| sm.checkPermission(new RuntimePermission("setContextClassLoader")); |
| } |
| return null; |
| } |
| |
| /** |
| * Lazy initialization: if this instance of manager is the global |
| * manager then this method will read the initial configuration and |
| * add the root logger and global logger by calling addLogger(). |
| * |
| * Note that it is subtly different from what we do in LoggerContext. |
| * In LoggerContext we're patching up the logger context tree in order to add |
| * the root and global logger *to the context tree*. |
| * |
| * For this to work, addLogger() must have already have been called |
| * once on the LogManager instance for the default logger being |
| * added. |
| * |
| * This is why ensureLogManagerInitialized() needs to be called before |
| * any logger is added to any logger context. |
| * |
| */ |
| private boolean initializedCalled = false; |
| private volatile boolean initializationDone = false; |
| final void ensureLogManagerInitialized() { |
| final LogManager owner = this; |
| if (initializationDone || owner != manager) { |
| // we don't want to do this twice, and we don't want to do |
| // this on private manager instances. |
| return; |
| } |
| |
| // Maybe another thread has called ensureLogManagerInitialized() |
| // before us and is still executing it. If so we will block until |
| // the log manager has finished initialized, then acquire the monitor, |
| // notice that initializationDone is now true and return. |
| // Otherwise - we have come here first! We will acquire the monitor, |
| // see that initializationDone is still false, and perform the |
| // initialization. |
| // |
| synchronized(this) { |
| // If initializedCalled is true it means that we're already in |
| // the process of initializing the LogManager in this thread. |
| // There has been a recursive call to ensureLogManagerInitialized(). |
| final boolean isRecursiveInitialization = (initializedCalled == true); |
| |
| assert initializedCalled || !initializationDone |
| : "Initialization can't be done if initialized has not been called!"; |
| |
| if (isRecursiveInitialization || initializationDone) { |
| // If isRecursiveInitialization is true it means that we're |
| // already in the process of initializing the LogManager in |
| // this thread. There has been a recursive call to |
| // ensureLogManagerInitialized(). We should not proceed as |
| // it would lead to infinite recursion. |
| // |
| // If initializationDone is true then it means the manager |
| // has finished initializing; just return: we're done. |
| return; |
| } |
| // Calling addLogger below will in turn call requiresDefaultLogger() |
| // which will call ensureLogManagerInitialized(). |
| // We use initializedCalled to break the recursion. |
| initializedCalled = true; |
| try { |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| assert rootLogger == null; |
| assert initializedCalled && !initializationDone; |
| |
| // Read configuration. |
| owner.readPrimordialConfiguration(); |
| |
| // Create and retain Logger for the root of the namespace. |
| owner.rootLogger = owner.new RootLogger(); |
| owner.addLogger(owner.rootLogger); |
| if (!owner.rootLogger.isLevelInitialized()) { |
| owner.rootLogger.setLevel(defaultLevel); |
| } |
| |
| // Adding the global Logger. |
| // Do not call Logger.getGlobal() here as this might trigger |
| // subtle inter-dependency issues. |
| @SuppressWarnings("deprecation") |
| final Logger global = Logger.global; |
| |
| // Make sure the global logger will be registered in the |
| // global manager |
| owner.addLogger(global); |
| return null; |
| } |
| }); |
| } finally { |
| initializationDone = true; |
| } |
| } |
| } |
| |
| /** |
| * Returns the global LogManager object. |
| * @return the global LogManager object |
| */ |
| public static LogManager getLogManager() { |
| if (manager != null) { |
| manager.ensureLogManagerInitialized(); |
| } |
| return manager; |
| } |
| |
| private void readPrimordialConfiguration() { |
| if (!readPrimordialConfiguration) { |
| synchronized (this) { |
| if (!readPrimordialConfiguration) { |
| // If System.in/out/err are null, it's a good |
| // indication that we're still in the |
| // bootstrapping phase |
| if (System.out == null) { |
| return; |
| } |
| readPrimordialConfiguration = true; |
| |
| try { |
| AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { |
| @Override |
| public Void run() throws Exception { |
| readConfiguration(); |
| |
| // Platform loggers begin to delegate to java.util.logging.Logger |
| sun.util.logging.PlatformLogger.redirectPlatformLoggers(); |
| return null; |
| } |
| }); |
| } catch (Exception ex) { |
| assert false : "Exception raised while reading logging configuration: " + ex; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds an event listener to be invoked when the logging |
| * properties are re-read. Adding multiple instances of |
| * the same event Listener results in multiple entries |
| * in the property event listener table. |
| * |
| * <p><b>WARNING:</b> This method is omitted from this class in all subset |
| * Profiles of Java SE that do not include the {@code java.beans} package. |
| * </p> |
| * |
| * @param l event listener |
| * @exception SecurityException if a security manager exists and if |
| * the caller does not have LoggingPermission("control"). |
| * @exception NullPointerException if the PropertyChangeListener is null. |
| * @deprecated The dependency on {@code PropertyChangeListener} creates a |
| * significant impediment to future modularization of the Java |
| * platform. This method will be removed in a future release. |
| * The global {@code LogManager} can detect changes to the |
| * logging configuration by overridding the {@link |
| * #readConfiguration readConfiguration} method. |
| */ |
| @Deprecated |
| public void addPropertyChangeListener(PropertyChangeListener l) throws SecurityException { |
| PropertyChangeListener listener = Objects.requireNonNull(l); |
| checkPermission(); |
| synchronized (listenerMap) { |
| // increment the registration count if already registered |
| Integer value = listenerMap.get(listener); |
| value = (value == null) ? 1 : (value + 1); |
| listenerMap.put(listener, value); |
| } |
| } |
| |
| /** |
| * Removes an event listener for property change events. |
| * If the same listener instance has been added to the listener table |
| * through multiple invocations of <CODE>addPropertyChangeListener</CODE>, |
| * then an equivalent number of |
| * <CODE>removePropertyChangeListener</CODE> invocations are required to remove |
| * all instances of that listener from the listener table. |
| * <P> |
| * Returns silently if the given listener is not found. |
| * |
| * <p><b>WARNING:</b> This method is omitted from this class in all subset |
| * Profiles of Java SE that do not include the {@code java.beans} package. |
| * </p> |
| * |
| * @param l event listener (can be null) |
| * @exception SecurityException if a security manager exists and if |
| * the caller does not have LoggingPermission("control"). |
| * @deprecated The dependency on {@code PropertyChangeListener} creates a |
| * significant impediment to future modularization of the Java |
| * platform. This method will be removed in a future release. |
| * The global {@code LogManager} can detect changes to the |
| * logging configuration by overridding the {@link |
| * #readConfiguration readConfiguration} method. |
| */ |
| @Deprecated |
| public void removePropertyChangeListener(PropertyChangeListener l) throws SecurityException { |
| checkPermission(); |
| if (l != null) { |
| PropertyChangeListener listener = l; |
| synchronized (listenerMap) { |
| Integer value = listenerMap.get(listener); |
| if (value != null) { |
| // remove from map if registration count is 1, otherwise |
| // just decrement its count |
| int i = value.intValue(); |
| if (i == 1) { |
| listenerMap.remove(listener); |
| } else { |
| assert i > 1; |
| listenerMap.put(listener, i - 1); |
| } |
| } |
| } |
| } |
| } |
| |
| // LoggerContext maps from AppContext |
| private WeakHashMap<Object, LoggerContext> contextsMap = null; |
| |
| // Returns the LoggerContext for the user code (i.e. application or AppContext). |
| // Loggers are isolated from each AppContext. |
| private LoggerContext getUserContext() { |
| // Android-changed: Remove AWT specific hooks. |
| return userContext; |
| } |
| |
| // The system context. |
| final LoggerContext getSystemContext() { |
| return systemContext; |
| } |
| |
| private List<LoggerContext> contexts() { |
| List<LoggerContext> cxs = new ArrayList<>(); |
| cxs.add(getSystemContext()); |
| cxs.add(getUserContext()); |
| return cxs; |
| } |
| |
| // Find or create a specified logger instance. If a logger has |
| // already been created with the given name it is returned. |
| // Otherwise a new logger instance is created and registered |
| // in the LogManager global namespace. |
| // This method will always return a non-null Logger object. |
| // Synchronization is not required here. All synchronization for |
| // adding a new Logger object is handled by addLogger(). |
| // |
| // This method must delegate to the LogManager implementation to |
| // add a new Logger or return the one that has been added previously |
| // as a LogManager subclass may override the addLogger, getLogger, |
| // readConfiguration, and other methods. |
| Logger demandLogger(String name, String resourceBundleName, Class<?> caller) { |
| Logger result = getLogger(name); |
| if (result == null) { |
| // only allocate the new logger once |
| Logger newLogger = new Logger(name, resourceBundleName, caller, this, false); |
| do { |
| if (addLogger(newLogger)) { |
| // We successfully added the new Logger that we |
| // created above so return it without refetching. |
| return newLogger; |
| } |
| |
| // We didn't add the new Logger that we created above |
| // because another thread added a Logger with the same |
| // name after our null check above and before our call |
| // to addLogger(). We have to refetch the Logger because |
| // addLogger() returns a boolean instead of the Logger |
| // reference itself. However, if the thread that created |
| // the other Logger is not holding a strong reference to |
| // the other Logger, then it is possible for the other |
| // Logger to be GC'ed after we saw it in addLogger() and |
| // before we can refetch it. If it has been GC'ed then |
| // we'll just loop around and try again. |
| result = getLogger(name); |
| } while (result == null); |
| } |
| return result; |
| } |
| |
| Logger demandSystemLogger(String name, String resourceBundleName) { |
| // Add a system logger in the system context's namespace |
| final Logger sysLogger = getSystemContext().demandLogger(name, resourceBundleName); |
| |
| // Add the system logger to the LogManager's namespace if not exist |
| // so that there is only one single logger of the given name. |
| // System loggers are visible to applications unless a logger of |
| // the same name has been added. |
| Logger logger; |
| do { |
| // First attempt to call addLogger instead of getLogger |
| // This would avoid potential bug in custom LogManager.getLogger |
| // implementation that adds a logger if does not exist |
| if (addLogger(sysLogger)) { |
| // successfully added the new system logger |
| logger = sysLogger; |
| } else { |
| logger = getLogger(name); |
| } |
| } while (logger == null); |
| |
| // LogManager will set the sysLogger's handlers via LogManager.addLogger method. |
| if (logger != sysLogger && sysLogger.accessCheckedHandlers().length == 0) { |
| // if logger already exists but handlers not set |
| final Logger l = logger; |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| for (Handler hdl : l.accessCheckedHandlers()) { |
| sysLogger.addHandler(hdl); |
| } |
| return null; |
| } |
| }); |
| } |
| return sysLogger; |
| } |
| |
| // Android-added: getClassInstance helper method, used in several places in this class. |
| private static Class getClassInstance(String cname) throws ClassNotFoundException { |
| try { |
| return ClassLoader.getSystemClassLoader().loadClass(cname); |
| } catch (ClassNotFoundException ex) { |
| return Thread.currentThread().getContextClassLoader().loadClass(cname); |
| } |
| } |
| |
| // LoggerContext maintains the logger namespace per context. |
| // The default LogManager implementation has one system context and user |
| // context. The system context is used to maintain the namespace for |
| // all system loggers and is queried by the system code. If a system logger |
| // doesn't exist in the user context, it'll also be added to the user context. |
| // The user context is queried by the user code and all other loggers are |
| // added in the user context. |
| class LoggerContext { |
| // Table of named Loggers that maps names to Loggers. |
| private final Hashtable<String,LoggerWeakRef> namedLoggers = new Hashtable<>(); |
| // Tree of named Loggers |
| private final LogNode root; |
| private LoggerContext() { |
| this.root = new LogNode(null, this); |
| } |
| |
| |
| // Tells whether default loggers are required in this context. |
| // If true, the default loggers will be lazily added. |
| final boolean requiresDefaultLoggers() { |
| final boolean requiresDefaultLoggers = (getOwner() == manager); |
| if (requiresDefaultLoggers) { |
| getOwner().ensureLogManagerInitialized(); |
| } |
| return requiresDefaultLoggers; |
| } |
| |
| // This context's LogManager. |
| final LogManager getOwner() { |
| return LogManager.this; |
| } |
| |
| // This context owner's root logger, which if not null, and if |
| // the context requires default loggers, will be added to the context |
| // logger's tree. |
| final Logger getRootLogger() { |
| return getOwner().rootLogger; |
| } |
| |
| // The global logger, which if not null, and if |
| // the context requires default loggers, will be added to the context |
| // logger's tree. |
| final Logger getGlobalLogger() { |
| // Android-changed: Fix SuppressWarnings from "deprecated" to "deprecation". |
| @SuppressWarnings("deprecation") // avoids initialization cycles. |
| final Logger global = Logger.global; |
| return global; |
| } |
| |
| Logger demandLogger(String name, String resourceBundleName) { |
| // a LogManager subclass may have its own implementation to add and |
| // get a Logger. So delegate to the LogManager to do the work. |
| final LogManager owner = getOwner(); |
| return owner.demandLogger(name, resourceBundleName, null); |
| } |
| |
| |
| // Due to subtle deadlock issues getUserContext() no longer |
| // calls addLocalLogger(rootLogger); |
| // Therefore - we need to add the default loggers later on. |
| // Checks that the context is properly initialized |
| // This is necessary before calling e.g. find(name) |
| // or getLoggerNames() |
| // |
| private void ensureInitialized() { |
| if (requiresDefaultLoggers()) { |
| // Ensure that the root and global loggers are set. |
| ensureDefaultLogger(getRootLogger()); |
| ensureDefaultLogger(getGlobalLogger()); |
| } |
| } |
| |
| |
| synchronized Logger findLogger(String name) { |
| // ensure that this context is properly initialized before |
| // looking for loggers. |
| ensureInitialized(); |
| LoggerWeakRef ref = namedLoggers.get(name); |
| if (ref == null) { |
| return null; |
| } |
| Logger logger = ref.get(); |
| if (logger == null) { |
| // Hashtable holds stale weak reference |
| // to a logger which has been GC-ed. |
| ref.dispose(); |
| } |
| return logger; |
| } |
| |
| // This method is called before adding a logger to the |
| // context. |
| // 'logger' is the context that will be added. |
| // This method will ensure that the defaults loggers are added |
| // before adding 'logger'. |
| // |
| private void ensureAllDefaultLoggers(Logger logger) { |
| if (requiresDefaultLoggers()) { |
| final String name = logger.getName(); |
| if (!name.isEmpty()) { |
| ensureDefaultLogger(getRootLogger()); |
| if (!Logger.GLOBAL_LOGGER_NAME.equals(name)) { |
| ensureDefaultLogger(getGlobalLogger()); |
| } |
| } |
| } |
| } |
| |
| private void ensureDefaultLogger(Logger logger) { |
| // Used for lazy addition of root logger and global logger |
| // to a LoggerContext. |
| |
| // This check is simple sanity: we do not want that this |
| // method be called for anything else than Logger.global |
| // or owner.rootLogger. |
| if (!requiresDefaultLoggers() || logger == null |
| || logger != Logger.global && logger != LogManager.this.rootLogger) { |
| |
| // the case where we have a non null logger which is neither |
| // Logger.global nor manager.rootLogger indicates a serious |
| // issue - as ensureDefaultLogger should never be called |
| // with any other loggers than one of these two (or null - if |
| // e.g manager.rootLogger is not yet initialized)... |
| assert logger == null; |
| |
| return; |
| } |
| |
| // Adds the logger if it's not already there. |
| if (!namedLoggers.containsKey(logger.getName())) { |
| // It is important to prevent addLocalLogger to |
| // call ensureAllDefaultLoggers when we're in the process |
| // off adding one of those default loggers - as this would |
| // immediately cause a stack overflow. |
| // Therefore we must pass addDefaultLoggersIfNeeded=false, |
| // even if requiresDefaultLoggers is true. |
| addLocalLogger(logger, false); |
| } |
| } |
| |
| boolean addLocalLogger(Logger logger) { |
| // no need to add default loggers if it's not required |
| return addLocalLogger(logger, requiresDefaultLoggers()); |
| } |
| |
| // Add a logger to this context. This method will only set its level |
| // and process parent loggers. It doesn't set its handlers. |
| synchronized boolean addLocalLogger(Logger logger, boolean addDefaultLoggersIfNeeded) { |
| // addDefaultLoggersIfNeeded serves to break recursion when adding |
| // default loggers. If we're adding one of the default loggers |
| // (we're being called from ensureDefaultLogger()) then |
| // addDefaultLoggersIfNeeded will be false: we don't want to |
| // call ensureAllDefaultLoggers again. |
| // |
| // Note: addDefaultLoggersIfNeeded can also be false when |
| // requiresDefaultLoggers is false - since calling |
| // ensureAllDefaultLoggers would have no effect in this case. |
| if (addDefaultLoggersIfNeeded) { |
| ensureAllDefaultLoggers(logger); |
| } |
| |
| final String name = logger.getName(); |
| if (name == null) { |
| throw new NullPointerException(); |
| } |
| LoggerWeakRef ref = namedLoggers.get(name); |
| if (ref != null) { |
| // Android-changed: Use refersTo(). |
| if (ref.refersTo(null)) { |
| // It's possible that the Logger was GC'ed after a |
| // drainLoggerRefQueueBounded() call above so allow |
| // a new one to be registered. |
| ref.dispose(); |
| } else { |
| // We already have a registered logger with the given name. |
| return false; |
| } |
| } |
| |
| // We're adding a new logger. |
| // Note that we are creating a weak reference here. |
| final LogManager owner = getOwner(); |
| logger.setLogManager(owner); |
| ref = owner.new LoggerWeakRef(logger); |
| namedLoggers.put(name, ref); |
| |
| // Apply any initial level defined for the new logger, unless |
| // the logger's level is already initialized |
| Level level = owner.getLevelProperty(name + ".level", null); |
| if (level != null && !logger.isLevelInitialized()) { |
| doSetLevel(logger, level); |
| } |
| |
| // instantiation of the handler is done in the LogManager.addLogger |
| // implementation as a handler class may be only visible to LogManager |
| // subclass for the custom log manager case |
| processParentHandlers(logger, name); |
| |
| // Find the new node and its parent. |
| LogNode node = getNode(name); |
| node.loggerRef = ref; |
| Logger parent = null; |
| LogNode nodep = node.parent; |
| while (nodep != null) { |
| LoggerWeakRef nodeRef = nodep.loggerRef; |
| if (nodeRef != null) { |
| parent = nodeRef.get(); |
| if (parent != null) { |
| break; |
| } |
| } |
| nodep = nodep.parent; |
| } |
| |
| if (parent != null) { |
| doSetParent(logger, parent); |
| } |
| // Walk over the children and tell them we are their new parent. |
| node.walkAndSetParent(logger); |
| // new LogNode is ready so tell the LoggerWeakRef about it |
| ref.setNode(node); |
| return true; |
| } |
| |
| synchronized void removeLoggerRef(String name, LoggerWeakRef ref) { |
| namedLoggers.remove(name, ref); |
| } |
| |
| synchronized Enumeration<String> getLoggerNames() { |
| // ensure that this context is properly initialized before |
| // returning logger names. |
| ensureInitialized(); |
| return namedLoggers.keys(); |
| } |
| |
| // If logger.getUseParentHandlers() returns 'true' and any of the logger's |
| // parents have levels or handlers defined, make sure they are instantiated. |
| private void processParentHandlers(final Logger logger, final String name) { |
| final LogManager owner = getOwner(); |
| AccessController.doPrivileged(new PrivilegedAction<Void>() { |
| @Override |
| public Void run() { |
| if (logger != owner.rootLogger) { |
| boolean useParent = owner.getBooleanProperty(name + ".useParentHandlers", true); |
| if (!useParent) { |
| logger.setUseParentHandlers(false); |
| } |
| } |
| return null; |
| } |
| }); |
| |
| int ix = 1; |
| for (;;) { |
| int ix2 = name.indexOf(".", ix); |
| if (ix2 < 0) { |
| break; |
| } |
| String pname = name.substring(0, ix2); |
| if (owner.getProperty(pname + ".level") != null || |
| owner.getProperty(pname + ".handlers") != null) { |
| // This pname has a level/handlers definition. |
| // Make sure it exists. |
| demandLogger(pname, null); |
| } |
| ix = ix2+1; |
| } |
| } |
| |
| // Gets a node in our tree of logger nodes. |
| // If necessary, create it. |
| LogNode getNode(String name) { |
| if (name == null || name.equals("")) { |
| return root; |
| } |
| LogNode node = root; |
| while (name.length() > 0) { |
| int ix = name.indexOf("."); |
| String head; |
| if (ix > 0) { |
| head = name.substring(0, ix); |
| name = name.substring(ix + 1); |
| } else { |
| head = name; |
| name = ""; |
| } |
| if (node.children == null) { |
| node.children = new HashMap<>(); |
| } |
| LogNode child = node.children.get(head); |
| if (child == null) { |
| child = new LogNode(node, this); |
| node.children.put(head, child); |
| } |
| node = child; |
| } |
| return node; |
| } |
| } |
| |
| final class SystemLoggerContext extends LoggerContext { |
| // Add a system logger in the system context's namespace as well as |
| // in the LogManager's namespace if not exist so that there is only |
| // one single logger of the given name. System loggers are visible |
| // to applications unless a logger of the same name has been added. |
| @Override |
| Logger demandLogger(String name, String resourceBundleName) { |
| Logger result = findLogger(name); |
| if (result == null) { |
| // only allocate the new system logger once |
| Logger newLogger = new Logger(name, resourceBundleName, null, getOwner(), true); |
| do { |
| if (addLocalLogger(newLogger)) { |
| // We successfully added the new Logger that we |
| // created above so return it without refetching. |
| result = newLogger; |
| } else { |
| // We didn't add the new Logger that we created above |
| // because another thread added a Logger with the same |
| // name after our null check above and before our call |
| // to addLogger(). We have to refetch the Logger because |
| // addLogger() returns a boolean instead of the Logger |
| // reference itself. However, if the thread that created |
| // the other Logger is not holding a strong reference to |
| // the other Logger, then it is possible for the other |
| // Logger to be GC'ed after we saw it in addLogger() and |
| // before we can refetch it. If it has been GC'ed then |
| // we'll just loop around and try again. |
| result = findLogger(name); |
| } |
| } while (result == null); |
| } |
| return result; |
| } |
| } |
| |
| // Add new per logger handlers. |
| // We need to raise privilege here. All our decisions will |
| // be made based on the logging configuration, which can |
| // only be modified by trusted code. |
| private void loadLoggerHandlers(final Logger logger, final String name, |
| final String handlersPropertyName) |
| { |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| String names[] = parseClassNames(handlersPropertyName); |
| for (int i = 0; i < names.length; i++) { |
| String word = names[i]; |
| try { |
| // Android-changed: Fall back from the system to the context classloader. |
| // Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word); |
| Class<?> clz = getClassInstance(word); |
| Handler hdl = (Handler) clz.newInstance(); |
| // Check if there is a property defining the |
| // this handler's level. |
| String levs = getProperty(word + ".level"); |
| if (levs != null) { |
| Level l = Level.findLevel(levs); |
| if (l != null) { |
| hdl.setLevel(l); |
| } else { |
| // Probably a bad level. Drop through. |
| System.err.println("Can't set level for " + word); |
| } |
| } |
| // Add this Handler to the logger |
| logger.addHandler(hdl); |
| } catch (Exception ex) { |
| System.err.println("Can't load log handler \"" + word + "\""); |
| System.err.println("" + ex); |
| ex.printStackTrace(); |
| } |
| } |
| return null; |
| } |
| }); |
| } |
| |
| |
| // loggerRefQueue holds LoggerWeakRef objects for Logger objects |
| // that have been GC'ed. |
| private final ReferenceQueue<Logger> loggerRefQueue |
| = new ReferenceQueue<>(); |
| |
| // Package-level inner class. |
| // Helper class for managing WeakReferences to Logger objects. |
| // |
| // LogManager.namedLoggers |
| // - has weak references to all named Loggers |
| // - namedLoggers keeps the LoggerWeakRef objects for the named |
| // Loggers around until we can deal with the book keeping for |
| // the named Logger that is being GC'ed. |
| // LogManager.LogNode.loggerRef |
| // - has a weak reference to a named Logger |
| // - the LogNode will also keep the LoggerWeakRef objects for |
| // the named Loggers around; currently LogNodes never go away. |
| // Logger.kids |
| // - has a weak reference to each direct child Logger; this |
| // includes anonymous and named Loggers |
| // - anonymous Loggers are always children of the rootLogger |
| // which is a strong reference; rootLogger.kids keeps the |
| // LoggerWeakRef objects for the anonymous Loggers around |
| // until we can deal with the book keeping. |
| // |
| final class LoggerWeakRef extends WeakReference<Logger> { |
| private String name; // for namedLoggers cleanup |
| private LogNode node; // for loggerRef cleanup |
| private WeakReference<Logger> parentRef; // for kids cleanup |
| private boolean disposed = false; // avoid calling dispose twice |
| |
| LoggerWeakRef(Logger logger) { |
| super(logger, loggerRefQueue); |
| |
| name = logger.getName(); // save for namedLoggers cleanup |
| } |
| |
| // dispose of this LoggerWeakRef object |
| void dispose() { |
| // Avoid calling dispose twice. When a Logger is gc'ed, its |
| // LoggerWeakRef will be enqueued. |
| // However, a new logger of the same name may be added (or looked |
| // up) before the queue is drained. When that happens, dispose() |
| // will be called by addLocalLogger() or findLogger(). |
| // Later when the queue is drained, dispose() will be called again |
| // for the same LoggerWeakRef. Marking LoggerWeakRef as disposed |
| // avoids processing the data twice (even though the code should |
| // now be reentrant). |
| synchronized(this) { |
| // Note to maintainers: |
| // Be careful not to call any method that tries to acquire |
| // another lock from within this block - as this would surely |
| // lead to deadlocks, given that dispose() can be called by |
| // multiple threads, and from within different synchronized |
| // methods/blocks. |
| if (disposed) return; |
| disposed = true; |
| } |
| |
| final LogNode n = node; |
| if (n != null) { |
| // n.loggerRef can only be safely modified from within |
| // a lock on LoggerContext. removeLoggerRef is already |
| // synchronized on LoggerContext so calling |
| // n.context.removeLoggerRef from within this lock is safe. |
| synchronized (n.context) { |
| // if we have a LogNode, then we were a named Logger |
| // so clear namedLoggers weak ref to us |
| n.context.removeLoggerRef(name, this); |
| name = null; // clear our ref to the Logger's name |
| |
| // LogNode may have been reused - so only clear |
| // LogNode.loggerRef if LogNode.loggerRef == this |
| if (n.loggerRef == this) { |
| n.loggerRef = null; // clear LogNode's weak ref to us |
| } |
| node = null; // clear our ref to LogNode |
| } |
| } |
| |
| if (parentRef != null) { |
| // this LoggerWeakRef has or had a parent Logger |
| Logger parent = parentRef.get(); |
| if (parent != null) { |
| // the parent Logger is still there so clear the |
| // parent Logger's weak ref to us |
| parent.removeChildLogger(this); |
| } |
| parentRef = null; // clear our weak ref to the parent Logger |
| } |
| } |
| |
| // set the node field to the specified value |
| void setNode(LogNode node) { |
| this.node = node; |
| } |
| |
| // set the parentRef field to the specified value |
| void setParentRef(WeakReference<Logger> parentRef) { |
| this.parentRef = parentRef; |
| } |
| } |
| |
| // Package-level method. |
| // Drain some Logger objects that have been GC'ed. |
| // |
| // drainLoggerRefQueueBounded() is called by addLogger() below |
| // and by Logger.getAnonymousLogger(String) so we'll drain up to |
| // MAX_ITERATIONS GC'ed Loggers for every Logger we add. |
| // |
| // On a WinXP VMware client, a MAX_ITERATIONS value of 400 gives |
| // us about a 50/50 mix in increased weak ref counts versus |
| // decreased weak ref counts in the AnonLoggerWeakRefLeak test. |
| // Here are stats for cleaning up sets of 400 anonymous Loggers: |
| // - test duration 1 minute |
| // - sample size of 125 sets of 400 |
| // - average: 1.99 ms |
| // - minimum: 0.57 ms |
| // - maximum: 25.3 ms |
| // |
| // The same config gives us a better decreased weak ref count |
| // than increased weak ref count in the LoggerWeakRefLeak test. |
| // Here are stats for cleaning up sets of 400 named Loggers: |
| // - test duration 2 minutes |
| // - sample size of 506 sets of 400 |
| // - average: 0.57 ms |
| // - minimum: 0.02 ms |
| // - maximum: 10.9 ms |
| // |
| private final static int MAX_ITERATIONS = 400; |
| final void drainLoggerRefQueueBounded() { |
| for (int i = 0; i < MAX_ITERATIONS; i++) { |
| if (loggerRefQueue == null) { |
| // haven't finished loading LogManager yet |
| break; |
| } |
| |
| LoggerWeakRef ref = (LoggerWeakRef) loggerRefQueue.poll(); |
| if (ref == null) { |
| break; |
| } |
| // a Logger object has been GC'ed so clean it up |
| ref.dispose(); |
| } |
| } |
| |
| /** |
| * Add a named logger. This does nothing and returns false if a logger |
| * with the same name is already registered. |
| * <p> |
| * The Logger factory methods call this method to register each |
| * newly created Logger. |
| * <p> |
| * The application should retain its own reference to the Logger |
| * object to avoid it being garbage collected. The LogManager |
| * may only retain a weak reference. |
| * |
| * @param logger the new logger. |
| * @return true if the argument logger was registered successfully, |
| * false if a logger of that name already exists. |
| * @exception NullPointerException if the logger name is null. |
| */ |
| public boolean addLogger(Logger logger) { |
| final String name = logger.getName(); |
| if (name == null) { |
| throw new NullPointerException(); |
| } |
| drainLoggerRefQueueBounded(); |
| LoggerContext cx = getUserContext(); |
| if (cx.addLocalLogger(logger)) { |
| // Do we have a per logger handler too? |
| // Note: this will add a 200ms penalty |
| loadLoggerHandlers(logger, name, name + ".handlers"); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| // Private method to set a level on a logger. |
| // If necessary, we raise privilege before doing the call. |
| private static void doSetLevel(final Logger logger, final Level level) { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm == null) { |
| // There is no security manager, so things are easy. |
| logger.setLevel(level); |
| return; |
| } |
| // There is a security manager. Raise privilege before |
| // calling setLevel. |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| logger.setLevel(level); |
| return null; |
| }}); |
| } |
| |
| // Private method to set a parent on a logger. |
| // If necessary, we raise privilege before doing the setParent call. |
| private static void doSetParent(final Logger logger, final Logger parent) { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm == null) { |
| // There is no security manager, so things are easy. |
| logger.setParent(parent); |
| return; |
| } |
| // There is a security manager. Raise privilege before |
| // calling setParent. |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| logger.setParent(parent); |
| return null; |
| }}); |
| } |
| |
| /** |
| * Method to find a named logger. |
| * <p> |
| * Note that since untrusted code may create loggers with |
| * arbitrary names this method should not be relied on to |
| * find Loggers for security sensitive logging. |
| * It is also important to note that the Logger associated with the |
| * String {@code name} may be garbage collected at any time if there |
| * is no strong reference to the Logger. The caller of this method |
| * must check the return value for null in order to properly handle |
| * the case where the Logger has been garbage collected. |
| * <p> |
| * @param name name of the logger |
| * @return matching logger or null if none is found |
| */ |
| public Logger getLogger(String name) { |
| return getUserContext().findLogger(name); |
| } |
| |
| /** |
| * Get an enumeration of known logger names. |
| * <p> |
| * Note: Loggers may be added dynamically as new classes are loaded. |
| * This method only reports on the loggers that are currently registered. |
| * It is also important to note that this method only returns the name |
| * of a Logger, not a strong reference to the Logger itself. |
| * The returned String does nothing to prevent the Logger from being |
| * garbage collected. In particular, if the returned name is passed |
| * to {@code LogManager.getLogger()}, then the caller must check the |
| * return value from {@code LogManager.getLogger()} for null to properly |
| * handle the case where the Logger has been garbage collected in the |
| * time since its name was returned by this method. |
| * <p> |
| * @return enumeration of logger name strings |
| */ |
| public Enumeration<String> getLoggerNames() { |
| return getUserContext().getLoggerNames(); |
| } |
| |
| /** |
| * Reinitialize the logging properties and reread the logging configuration. |
| * <p> |
| * The same rules are used for locating the configuration properties |
| * as are used at startup. So normally the logging properties will |
| * be re-read from the same file that was used at startup. |
| * <P> |
| * Any log level definitions in the new configuration file will be |
| * applied using Logger.setLevel(), if the target Logger exists. |
| * <p> |
| * A PropertyChangeEvent will be fired after the properties are read. |
| * |
| * @exception SecurityException if a security manager exists and if |
| * the caller does not have LoggingPermission("control"). |
| * @exception IOException if there are IO problems reading the configuration. |
| */ |
| public void readConfiguration() throws IOException, SecurityException { |
| checkPermission(); |
| |
| // if a configuration class is specified, load it and use it. |
| String cname = System.getProperty("java.util.logging.config.class"); |
| if (cname != null) { |
| try { |
| // Instantiate the named class. It is its constructor's |
| // responsibility to initialize the logging configuration, by |
| // calling readConfiguration(InputStream) with a suitable stream. |
| // Android-changed: Extract logic into the getClassInstance() helper. |
| getClassInstance(cname).newInstance(); |
| return; |
| } catch (Exception ex) { |
| System.err.println("Logging configuration class \"" + cname + "\" failed"); |
| System.err.println("" + ex); |
| // keep going and useful config file. |
| } |
| } |
| |
| String fname = System.getProperty("java.util.logging.config.file"); |
| if (fname == null) { |
| fname = System.getProperty("java.home"); |
| if (fname == null) { |
| throw new Error("Can't find java.home ??"); |
| } |
| File f = new File(fname, "lib"); |
| f = new File(f, "logging.properties"); |
| fname = f.getCanonicalPath(); |
| } |
| |
| // BEGIN Android-changed: Look in the boot class-path jar files for the logging.properties. |
| // It may not be present in the file system. |
| /* |
| try (final InputStream in = new FileInputStream(fname)) { |
| final BufferedInputStream bin = new BufferedInputStream(in); |
| readConfiguration(bin); |
| } |
| */ |
| InputStream in; |
| try { |
| in = new FileInputStream(fname); |
| } catch (Exception e) { |
| in = LogManager.class.getResourceAsStream("logging.properties"); |
| if (in == null) { |
| throw e; |
| } |
| } |
| |
| BufferedInputStream bin = new BufferedInputStream(in); |
| try { |
| readConfiguration(bin); |
| } finally { |
| if (in != null) { |
| in.close(); |
| } |
| } |
| // END Android-changed: Look in the boot class-path jar files for the logging.properties. |
| } |
| |
| /** |
| * Reset the logging configuration. |
| * <p> |
| * For all named loggers, the reset operation removes and closes |
| * all Handlers and (except for the root logger) sets the level |
| * to null. The root logger's level is set to Level.INFO. |
| * |
| * @exception SecurityException if a security manager exists and if |
| * the caller does not have LoggingPermission("control"). |
| */ |
| |
| public void reset() throws SecurityException { |
| checkPermission(); |
| synchronized (this) { |
| props = new Properties(); |
| // Since we are doing a reset we no longer want to initialize |
| // the global handlers, if they haven't been initialized yet. |
| initializedGlobalHandlers = true; |
| } |
| for (LoggerContext cx : contexts()) { |
| Enumeration<String> enum_ = cx.getLoggerNames(); |
| while (enum_.hasMoreElements()) { |
| String name = enum_.nextElement(); |
| Logger logger = cx.findLogger(name); |
| if (logger != null) { |
| resetLogger(logger); |
| } |
| } |
| } |
| } |
| |
| // Private method to reset an individual target logger. |
| private void resetLogger(Logger logger) { |
| // Close all the Logger's handlers. |
| Handler[] targets = logger.getHandlers(); |
| for (int i = 0; i < targets.length; i++) { |
| Handler h = targets[i]; |
| logger.removeHandler(h); |
| try { |
| h.close(); |
| } catch (Exception ex) { |
| // Problems closing a handler? Keep going... |
| } |
| } |
| String name = logger.getName(); |
| if (name != null && name.equals("")) { |
| // This is the root logger. |
| logger.setLevel(defaultLevel); |
| } else { |
| logger.setLevel(null); |
| } |
| } |
| |
| // get a list of whitespace separated classnames from a property. |
| private String[] parseClassNames(String propertyName) { |
| String hands = getProperty(propertyName); |
| if (hands == null) { |
| return new String[0]; |
| } |
| hands = hands.trim(); |
| int ix = 0; |
| final List<String> result = new ArrayList<>(); |
| while (ix < hands.length()) { |
| int end = ix; |
| while (end < hands.length()) { |
| if (Character.isWhitespace(hands.charAt(end))) { |
| break; |
| } |
| if (hands.charAt(end) == ',') { |
| break; |
| } |
| end++; |
| } |
| String word = hands.substring(ix, end); |
| ix = end+1; |
| word = word.trim(); |
| if (word.length() == 0) { |
| continue; |
| } |
| result.add(word); |
| } |
| return result.toArray(new String[result.size()]); |
| } |
| |
| /** |
| * Reinitialize the logging properties and reread the logging configuration |
| * from the given stream, which should be in java.util.Properties format. |
| * A PropertyChangeEvent will be fired after the properties are read. |
| * <p> |
| * Any log level definitions in the new configuration file will be |
| * applied using Logger.setLevel(), if the target Logger exists. |
| * |
| * @param ins stream to read properties from |
| * @exception SecurityException if a security manager exists and if |
| * the caller does not have LoggingPermission("control"). |
| * @exception IOException if there are problems reading from the stream. |
| */ |
| public void readConfiguration(InputStream ins) throws IOException, SecurityException { |
| checkPermission(); |
| reset(); |
| |
| // Load the properties |
| props.load(ins); |
| // Instantiate new configuration objects. |
| String names[] = parseClassNames("config"); |
| |
| for (int i = 0; i < names.length; i++) { |
| String word = names[i]; |
| try { |
| // Android-changed: Fall back from the system to the context classloader. |
| // Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(word); |
| // clz.newInstance(); |
| getClassInstance(word).newInstance(); |
| } catch (Exception ex) { |
| System.err.println("Can't load config class \"" + word + "\""); |
| System.err.println("" + ex); |
| // ex.printStackTrace(); |
| } |
| } |
| |
| // Set levels on any pre-existing loggers, based on the new properties. |
| setLevelsOnExistingLoggers(); |
| |
| // Notify any interested parties that our properties have changed. |
| // We first take a copy of the listener map so that we aren't holding any |
| // locks when calling the listeners. |
| Map<Object,Integer> listeners = null; |
| synchronized (listenerMap) { |
| if (!listenerMap.isEmpty()) |
| listeners = new HashMap<>(listenerMap); |
| } |
| if (listeners != null) { |
| assert Beans.isBeansPresent(); |
| Object ev = Beans.newPropertyChangeEvent(LogManager.class, null, null, null); |
| for (Map.Entry<Object,Integer> entry : listeners.entrySet()) { |
| Object listener = entry.getKey(); |
| int count = entry.getValue().intValue(); |
| for (int i = 0; i < count; i++) { |
| Beans.invokePropertyChange(listener, ev); |
| } |
| } |
| } |
| |
| |
| // Note that we need to reinitialize global handles when |
| // they are first referenced. |
| synchronized (this) { |
| initializedGlobalHandlers = false; |
| } |
| } |
| |
| /** |
| * Get the value of a logging property. |
| * The method returns null if the property is not found. |
| * @param name property name |
| * @return property value |
| */ |
| public String getProperty(String name) { |
| return props.getProperty(name); |
| } |
| |
| // Package private method to get a String property. |
| // If the property is not defined we return the given |
| // default value. |
| String getStringProperty(String name, String defaultValue) { |
| String val = getProperty(name); |
| if (val == null) { |
| return defaultValue; |
| } |
| return val.trim(); |
| } |
| |
| // Package private method to get an integer property. |
| // If the property is not defined or cannot be parsed |
| // we return the given default value. |
| int getIntProperty(String name, int defaultValue) { |
| String val = getProperty(name); |
| if (val == null) { |
| return defaultValue; |
| } |
| try { |
| return Integer.parseInt(val.trim()); |
| } catch (Exception ex) { |
| return defaultValue; |
| } |
| } |
| |
| // Package private method to get a boolean property. |
| // If the property is not defined or cannot be parsed |
| // we return the given default value. |
| boolean getBooleanProperty(String name, boolean defaultValue) { |
| String val = getProperty(name); |
| if (val == null) { |
| return defaultValue; |
| } |
| val = val.toLowerCase(); |
| if (val.equals("true") || val.equals("1")) { |
| return true; |
| } else if (val.equals("false") || val.equals("0")) { |
| return false; |
| } |
| return defaultValue; |
| } |
| |
| // Package private method to get a Level property. |
| // If the property is not defined or cannot be parsed |
| // we return the given default value. |
| Level getLevelProperty(String name, Level defaultValue) { |
| String val = getProperty(name); |
| if (val == null) { |
| return defaultValue; |
| } |
| Level l = Level.findLevel(val.trim()); |
| return l != null ? l : defaultValue; |
| } |
| |
| // Package private method to get a filter property. |
| // We return an instance of the class named by the "name" |
| // property. If the property is not defined or has problems |
| // we return the defaultValue. |
| Filter getFilterProperty(String name, Filter defaultValue) { |
| String val = getProperty(name); |
| try { |
| if (val != null) { |
| // Android-changed: Fall back from the system to the context classloader. |
| // Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(val); |
| // return (Filter) clz.newInstance(); |
| return (Filter) getClassInstance(val).newInstance(); |
| } |
| } catch (Exception ex) { |
| // We got one of a variety of exceptions in creating the |
| // class or creating an instance. |
| // Drop through. |
| } |
| // We got an exception. Return the defaultValue. |
| return defaultValue; |
| } |
| |
| |
| // Package private method to get a formatter property. |
| // We return an instance of the class named by the "name" |
| // property. If the property is not defined or has problems |
| // we return the defaultValue. |
| Formatter getFormatterProperty(String name, Formatter defaultValue) { |
| String val = getProperty(name); |
| try { |
| if (val != null) { |
| // Android-changed: Fall back from the system to the context classloader. |
| // Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(val); |
| // return (Formatter) clz.newInstance(); |
| return (Formatter) getClassInstance(val).newInstance(); |
| } |
| } catch (Exception ex) { |
| // We got one of a variety of exceptions in creating the |
| // class or creating an instance. |
| // Drop through. |
| } |
| // We got an exception. Return the defaultValue. |
| return defaultValue; |
| } |
| |
| // Private method to load the global handlers. |
| // We do the real work lazily, when the global handlers |
| // are first used. |
| private synchronized void initializeGlobalHandlers() { |
| if (initializedGlobalHandlers) { |
| return; |
| } |
| |
| initializedGlobalHandlers = true; |
| |
| if (deathImminent) { |
| // Aaargh... |
| // The VM is shutting down and our exit hook has been called. |
| // Avoid allocating global handlers. |
| return; |
| } |
| loadLoggerHandlers(rootLogger, null, "handlers"); |
| } |
| |
| private final Permission controlPermission = new LoggingPermission("control", null); |
| |
| void checkPermission() { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) |
| sm.checkPermission(controlPermission); |
| } |
| |
| /** |
| * Check that the current context is trusted to modify the logging |
| * configuration. This requires LoggingPermission("control"). |
| * <p> |
| * If the check fails we throw a SecurityException, otherwise |
| * we return normally. |
| * |
| * @exception SecurityException if a security manager exists and if |
| * the caller does not have LoggingPermission("control"). |
| */ |
| public void checkAccess() throws SecurityException { |
| checkPermission(); |
| } |
| |
| // Nested class to represent a node in our tree of named loggers. |
| private static class LogNode { |
| HashMap<String,LogNode> children; |
| LoggerWeakRef loggerRef; |
| LogNode parent; |
| final LoggerContext context; |
| |
| LogNode(LogNode parent, LoggerContext context) { |
| this.parent = parent; |
| this.context = context; |
| } |
| |
| // Recursive method to walk the tree below a node and set |
| // a new parent logger. |
| void walkAndSetParent(Logger parent) { |
| if (children == null) { |
| return; |
| } |
| Iterator<LogNode> values = children.values().iterator(); |
| while (values.hasNext()) { |
| LogNode node = values.next(); |
| LoggerWeakRef ref = node.loggerRef; |
| Logger logger = (ref == null) ? null : ref.get(); |
| if (logger == null) { |
| node.walkAndSetParent(parent); |
| } else { |
| doSetParent(logger, parent); |
| } |
| } |
| } |
| } |
| |
| // We use a subclass of Logger for the root logger, so |
| // that we only instantiate the global handlers when they |
| // are first needed. |
| private final class RootLogger extends Logger { |
| private RootLogger() { |
| // We do not call the protected Logger two args constructor here, |
| // to avoid calling LogManager.getLogManager() from within the |
| // RootLogger constructor. |
| super("", null, null, LogManager.this, true); |
| } |
| |
| @Override |
| public void log(LogRecord record) { |
| // Make sure that the global handlers have been instantiated. |
| initializeGlobalHandlers(); |
| super.log(record); |
| } |
| |
| @Override |
| public void addHandler(Handler h) { |
| initializeGlobalHandlers(); |
| super.addHandler(h); |
| } |
| |
| @Override |
| public void removeHandler(Handler h) { |
| initializeGlobalHandlers(); |
| super.removeHandler(h); |
| } |
| |
| @Override |
| Handler[] accessCheckedHandlers() { |
| initializeGlobalHandlers(); |
| return super.accessCheckedHandlers(); |
| } |
| } |
| |
| |
| // Private method to be called when the configuration has |
| // changed to apply any level settings to any pre-existing loggers. |
| synchronized private void setLevelsOnExistingLoggers() { |
| Enumeration<?> enum_ = props.propertyNames(); |
| while (enum_.hasMoreElements()) { |
| String key = (String)enum_.nextElement(); |
| if (!key.endsWith(".level")) { |
| // Not a level definition. |
| continue; |
| } |
| int ix = key.length() - 6; |
| String name = key.substring(0, ix); |
| Level level = getLevelProperty(key, null); |
| if (level == null) { |
| System.err.println("Bad level value for property: " + key); |
| continue; |
| } |
| for (LoggerContext cx : contexts()) { |
| Logger l = cx.findLogger(name); |
| if (l == null) { |
| continue; |
| } |
| l.setLevel(level); |
| } |
| } |
| } |
| |
| // Management Support |
| private static LoggingMXBean loggingMXBean = null; |
| // Android-removed: References to java.lang.management in javadoc. |
| /** |
| * String representation of the {@code ObjectName} for the management interface |
| * for the logging facility. |
| * |
| * @see java.util.logging.LoggingMXBean |
| * |
| * @since 1.5 |
| */ |
| public final static String LOGGING_MXBEAN_NAME |
| = "java.util.logging:type=Logging"; |
| |
| // Android-removed: References to java.lang.management in javadoc. |
| /** |
| * Returns <tt>LoggingMXBean</tt> for managing loggers. |
| * |
| * @return a {@link LoggingMXBean} object. |
| * |
| * @since 1.5 |
| */ |
| public static synchronized LoggingMXBean getLoggingMXBean() { |
| if (loggingMXBean == null) { |
| loggingMXBean = new Logging(); |
| } |
| return loggingMXBean; |
| } |
| |
| /** |
| * A class that provides access to the java.beans.PropertyChangeListener |
| * and java.beans.PropertyChangeEvent without creating a static dependency |
| * on java.beans. This class can be removed once the addPropertyChangeListener |
| * and removePropertyChangeListener methods are removed. |
| */ |
| private static class Beans { |
| private static final Class<?> propertyChangeListenerClass = |
| getClass("java.beans.PropertyChangeListener"); |
| |
| private static final Class<?> propertyChangeEventClass = |
| getClass("java.beans.PropertyChangeEvent"); |
| |
| private static final Method propertyChangeMethod = |
| getMethod(propertyChangeListenerClass, |
| "propertyChange", |
| propertyChangeEventClass); |
| |
| private static final Constructor<?> propertyEventCtor = |
| getConstructor(propertyChangeEventClass, |
| Object.class, |
| String.class, |
| Object.class, |
| Object.class); |
| |
| private static Class<?> getClass(String name) { |
| try { |
| return Class.forName(name, true, Beans.class.getClassLoader()); |
| } catch (ClassNotFoundException e) { |
| return null; |
| } |
| } |
| private static Constructor<?> getConstructor(Class<?> c, Class<?>... types) { |
| try { |
| return (c == null) ? null : c.getDeclaredConstructor(types); |
| } catch (NoSuchMethodException x) { |
| throw new AssertionError(x); |
| } |
| } |
| |
| private static Method getMethod(Class<?> c, String name, Class<?>... types) { |
| try { |
| return (c == null) ? null : c.getMethod(name, types); |
| } catch (NoSuchMethodException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if java.beans is present. |
| */ |
| static boolean isBeansPresent() { |
| return propertyChangeListenerClass != null && |
| propertyChangeEventClass != null; |
| } |
| |
| /** |
| * Returns a new PropertyChangeEvent with the given source, property |
| * name, old and new values. |
| */ |
| static Object newPropertyChangeEvent(Object source, String prop, |
| Object oldValue, Object newValue) |
| { |
| try { |
| return propertyEventCtor.newInstance(source, prop, oldValue, newValue); |
| } catch (InstantiationException | IllegalAccessException x) { |
| throw new AssertionError(x); |
| } catch (InvocationTargetException x) { |
| Throwable cause = x.getCause(); |
| if (cause instanceof Error) |
| throw (Error)cause; |
| if (cause instanceof RuntimeException) |
| throw (RuntimeException)cause; |
| throw new AssertionError(x); |
| } |
| } |
| |
| /** |
| * Invokes the given PropertyChangeListener's propertyChange method |
| * with the given event. |
| */ |
| static void invokePropertyChange(Object listener, Object ev) { |
| try { |
| propertyChangeMethod.invoke(listener, ev); |
| } catch (IllegalAccessException x) { |
| throw new AssertionError(x); |
| } catch (InvocationTargetException x) { |
| Throwable cause = x.getCause(); |
| if (cause instanceof Error) |
| throw (Error)cause; |
| if (cause instanceof RuntimeException) |
| throw (RuntimeException)cause; |
| throw new AssertionError(x); |
| } |
| } |
| } |
| } |