/* * Copyright (c) 2000, 2018, 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.util.concurrent.ConcurrentHashMap; import java.nio.file.Paths; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReentrantLock; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.internal.misc.JavaAWTAccess; import jdk.internal.misc.SharedSecrets; import sun.util.logging.internal.LoggingProviderImpl; import static jdk.internal.logger.DefaultLoggerFinder.isSystem; /** * There is a single global LogManager object that is used to * maintain a set of shared state about Loggers and log services. *

* This LogManager object: *

*

* The global LogManager object can be retrieved using LogManager.getLogManager(). * The LogManager object is created during class initialization and * cannot subsequently be changed. *

* At startup the LogManager class is located using the * java.util.logging.manager system property. * *

LogManager Configuration

* * A LogManager initializes the logging configuration via * the {@link #readConfiguration()} method during LogManager initialization. * By default, LogManager default configuration is used. * The logging configuration read by LogManager must be in the * {@linkplain Properties properties file} format. *

* The LogManager defines two optional system properties that allow control over * the initial configuration, as specified in the {@link #readConfiguration()} * method: *

*

* These two system properties may be specified on the command line to the "java" * command, or as system property definitions passed to JNI_CreateJavaVM. *

* The {@linkplain Properties properties} for loggers and Handlers will have * names starting with the dot-separated name for the handler or logger.
* The global logging properties may include: *

*

* 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. *

* 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. *

* 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. *

* 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; // 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 STATE_UNINITIALIZED in readConfiguration private static final int STATE_INITIALIZED = 0, // initial state STATE_INITIALIZING = 1, STATE_READING_CONFIG = 2, STATE_UNINITIALIZED = 3, STATE_SHUTDOWN = 4; // terminal state private volatile int globalHandlersState; // = STATE_INITIALIZED; // A concurrency lock for reset(), readConfiguration() and Cleaner. private final ReentrantLock configurationLock = new ReentrantLock(); // This list contains the loggers for which some handlers have been // explicitly configured in the configuration file. // It prevents these loggers from being arbitrarily garbage collected. private static final class CloseOnReset { private final Logger logger; private CloseOnReset(Logger ref) { this.logger = Objects.requireNonNull(ref); } @Override public boolean equals(Object other) { return (other instanceof CloseOnReset) && ((CloseOnReset)other).logger == logger; } @Override public int hashCode() { return System.identityHashCode(logger); } public Logger get() { return logger; } public static CloseOnReset create(Logger logger) { return new CloseOnReset(logger); } } private final CopyOnWriteArrayList closeOnResetLoggers = new CopyOnWriteArrayList<>(); private final Map listeners = Collections.synchronizedMap(new IdentityHashMap<>()); static { manager = AccessController.doPrivileged(new PrivilegedAction() { @Override public LogManager run() { LogManager mgr = null; String cname = null; try { cname = System.getProperty("java.util.logging.manager"); if (cname != null) { try { @SuppressWarnings("deprecation") Object tmp = ClassLoader.getSystemClassLoader() .loadClass(cname).newInstance(); mgr = (LogManager) tmp; } catch (ClassNotFoundException ex) { @SuppressWarnings("deprecation") Object tmp = Thread.currentThread() .getContextClassLoader().loadClass(cname).newInstance(); mgr = (LogManager) tmp; } } } 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() { super(null, null, "Logging-Cleaner", 0, false); /* 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. is completed // before synchronized block. Otherwise deadlocks are possible. LogManager mgr = manager; // set globalHandlersState to STATE_SHUTDOWN atomically so that // no attempts are made to (re)initialize the handlers or (re)read // the configuration again. This is terminal state. configurationLock.lock(); globalHandlersState = STATE_SHUTDOWN; configurationLock.unlock(); // 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. // configurationLock.lock(); try { // 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() { @Override public Object run() { assert rootLogger == null; assert initializedCalled && !initializationDone; // create root logger before reading primordial // configuration - to ensure that it will be added // before the global logger, and not after. final Logger root = owner.rootLogger = owner.new RootLogger(); // Read configuration. owner.readPrimordialConfiguration(); // Create and retain Logger for the root of the namespace. owner.addLogger(root); // Initialize level if not yet initialized if (!root.isLevelInitialized()) { root.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; } } finally { configurationLock.unlock(); } } /** * Returns the global LogManager object. * @return the global LogManager object */ public static LogManager getLogManager() { if (manager != null) { manager.ensureLogManagerInitialized(); } return manager; } private void readPrimordialConfiguration() { // must be called while holding configurationLock 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 { readConfiguration(); // Platform loggers begin to delegate to java.util.logging.Logger jdk.internal.logger.BootstrapLogger.redirectTemporaryLoggers(); } catch (Exception ex) { assert false : "Exception raised while reading logging configuration: " + ex; } } } // LoggerContext maps from AppContext private WeakHashMap contextsMap = null; // Returns the LoggerContext for the user code (i.e. application or AppContext). // Loggers are isolated from each AppContext. private LoggerContext getUserContext() { LoggerContext context = null; SecurityManager sm = System.getSecurityManager(); JavaAWTAccess javaAwtAccess = SharedSecrets.getJavaAWTAccess(); if (sm != null && javaAwtAccess != null) { // for each applet, it has its own LoggerContext isolated from others final Object ecx = javaAwtAccess.getAppletContext(); if (ecx != null) { synchronized (javaAwtAccess) { // find the AppContext of the applet code // will be null if we are in the main app context. if (contextsMap == null) { contextsMap = new WeakHashMap<>(); } context = contextsMap.get(ecx); if (context == null) { // Create a new LoggerContext for the applet. context = new LoggerContext(); contextsMap.put(ecx, context); } } } } // for standalone app, return userContext return context != null ? context : userContext; } // The system context. final LoggerContext getSystemContext() { return systemContext; } private List contexts() { List 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) { final Module module = caller == null ? null : caller.getModule(); return demandLogger(name, resourceBundleName, module); } Logger demandLogger(String name, String resourceBundleName, Module module) { Logger result = getLogger(name); if (result == null) { // only allocate the new logger once Logger newLogger = new Logger(name, resourceBundleName, module, 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, Class caller) { final Module module = caller == null ? null : caller.getModule(); return demandSystemLogger(name, resourceBundleName, module); } Logger demandSystemLogger(String name, String resourceBundleName, Module module) { // Add a system logger in the system context's namespace final Logger sysLogger = getSystemContext() .demandLogger(name, resourceBundleName, module); // 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) { // if logger already exists we merge the two logger configurations. final Logger l = logger; AccessController.doPrivileged(new PrivilegedAction() { @Override public Void run() { l.mergeWithSystemLogger(sysLogger); return null; } }); } return sysLogger; } // 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 ConcurrentHashMap namedLoggers = new ConcurrentHashMap<>(); // 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() { @SuppressWarnings("deprecation") // avoids initialization cycles. final Logger global = Logger.global; return global; } Logger demandLogger(String name, String resourceBundleName, Module module) { // 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, module); } // 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()); } } Logger findLogger(String name) { // Attempt to find logger without locking. LoggerWeakRef ref = namedLoggers.get(name); Logger logger = ref == null ? null : ref.get(); // if logger is not null, then we can return it right away. // if name is "" or "global" and logger is null // we need to fall through and check that this context is // initialized. // if ref is not null and logger is null we also need to // fall through. if (logger != null || (ref == null && !name.isEmpty() && !name.equals(Logger.GLOBAL_LOGGER_NAME))) { return logger; } // We either found a stale reference, or we were looking for // "" or "global" and didn't find them. // Make sure context is initialized (has the default loggers), // and look up again, cleaning the stale reference if it hasn't // been cleaned up in between. All this needs to be done inside // a synchronized block. synchronized(this) { // ensure that this context is properly initialized before // looking for loggers. ensureInitialized(); ref = namedLoggers.get(name); if (ref == null) { return null; } logger = ref.get(); if (logger == null) { // The namedLoggers map 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 != getGlobalLogger() && 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) { if (ref.get() == 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); // 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, VisitedLoggers.NEVER); // 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); // Do not publish 'ref' in namedLoggers before the logger tree // is fully updated - because the named logger will be visible as // soon as it is published in namedLoggers (findLogger takes // benefit of the ConcurrentHashMap implementation of namedLoggers // to avoid synchronizing on retrieval when that is possible). namedLoggers.put(name, ref); return true; } void removeLoggerRef(String name, LoggerWeakRef ref) { namedLoggers.remove(name, ref); } synchronized Enumeration getLoggerNames() { // ensure that this context is properly initialized before // returning logger names. ensureInitialized(); return Collections.enumeration(namedLoggers.keySet()); } // 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, Predicate visited) { final LogManager owner = getOwner(); AccessController.doPrivileged(new PrivilegedAction() { @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. if (visited.test(demandLogger(pname, null, null))) { break; } } 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, Module module) { Logger result = findLogger(name); if (result == null) { // only allocate the new system logger once Logger newLogger = new Logger(name, resourceBundleName, module, 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() { @Override public Void run() { setLoggerHandlers(logger, name, handlersPropertyName, createLoggerHandlers(name, handlersPropertyName)); return null; } }); } private void setLoggerHandlers(final Logger logger, final String name, final String handlersPropertyName, List handlers) { final boolean ensureCloseOnReset = ! handlers.isEmpty() && getBooleanProperty(handlersPropertyName + ".ensureCloseOnReset",true); int count = 0; for (Handler hdl : handlers) { logger.addHandler(hdl); if (++count == 1 && ensureCloseOnReset) { // add this logger to the closeOnResetLoggers list. closeOnResetLoggers.addIfAbsent(CloseOnReset.create(logger)); } } } private List createLoggerHandlers(final String name, final String handlersPropertyName) { String names[] = parseClassNames(handlersPropertyName); List handlers = new ArrayList<>(names.length); for (String type : names) { try { @SuppressWarnings("deprecation") Object o = ClassLoader.getSystemClassLoader().loadClass(type).newInstance(); Handler hdl = (Handler) o; // Check if there is a property defining the // this handler's level. String levs = getProperty(type + ".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 " + type); } } // Add this Handler to the logger handlers.add(hdl); } catch (Exception ex) { System.err.println("Can't load log handler \"" + type + "\""); System.err.println("" + ex); ex.printStackTrace(); } } return handlers; } // loggerRefQueue holds LoggerWeakRef objects for Logger objects // that have been GC'ed. private final ReferenceQueue 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 { private String name; // for namedLoggers cleanup private LogNode node; // for loggerRef cleanup private WeakReference 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 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(); } } @SuppressWarnings("deprecation") private boolean isSpecialLogger(Logger logger) { assert logger != null; return logger == rootLogger || logger == Logger.global; } /** * Add a named logger. This does nothing and returns false if a logger * with the same name is already registered. *

* The Logger factory methods call this method to register each * newly created Logger. *

* 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 if (isSpecialLogger(logger) && !initializationDone && initializedCalled && configurationLock.isHeldByCurrentThread()) { // Called just after reading the primordial configuration, in // the same thread that read it. // The root and global logger would already be present in the context // by this point, but we would not have called loadLoggerHandlers // yet. // For backward compatibility: add any handlers configured using // ".handlers" to the root logger, or any handlers configured // with "global.handlers" to the global logger. 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() { @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() { @Override public Object run() { logger.setParent(parent); return null; }}); } /** * Method to find a named logger. *

* 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. * * @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. *

* 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. * * @return enumeration of logger name strings */ public Enumeration getLoggerNames() { return getUserContext().getLoggerNames(); } /** * Reads and initializes the logging configuration. *

* If the "java.util.logging.config.class" system 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 {@code readConfiguration(InputStream)} * to define properties in the LogManager. *

* If "java.util.logging.config.class" system property is not set, * then this method will read the initial configuration from a properties * file and calls the {@link #readConfiguration(InputStream)} method to initialize * the configuration. The "java.util.logging.config.file" system property can be used * to specify the properties file that will be read as the initial configuration; * if not set, then the LogManager default configuration is used. * The default configuration is typically loaded from the * properties file "{@code conf/logging.properties}" in the Java installation * directory. * *

* Any {@linkplain #addConfigurationListener registered configuration * listener} will be invoked after the properties are read. * * @apiNote This {@code readConfiguration} method should only be used for * initializing the configuration during LogManager initialization or * used with the "java.util.logging.config.class" property. * When this method is called after loggers have been created, and * the "java.util.logging.config.class" system property is not set, all * existing loggers will be {@linkplain #reset() reset}. Then any * existing loggers that have a level property specified in the new * configuration stream will be {@linkplain * Logger#setLevel(java.util.logging.Level) set} to the specified log level. *

* To properly update the logging configuration, use the * {@link #updateConfiguration(java.util.function.Function)} or * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)} * methods instead. * * @throws SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). * @throws 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. try { Class clz = ClassLoader.getSystemClassLoader().loadClass(cname); @SuppressWarnings("deprecation") Object witness = clz.newInstance(); return; } catch (ClassNotFoundException ex) { Class clz = Thread.currentThread().getContextClassLoader().loadClass(cname); @SuppressWarnings("deprecation") Object witness = clz.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 = getConfigurationFileName(); try (final InputStream in = new FileInputStream(fname)) { final BufferedInputStream bin = new BufferedInputStream(in); readConfiguration(bin); } } String getConfigurationFileName() throws IOException { 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 ??"); } fname = Paths.get(fname, "conf", "logging.properties") .toAbsolutePath().normalize().toString(); } return fname; } /** * Reset the logging configuration. *

* For all named loggers, the reset operation removes and closes * all Handlers and (except for the root logger) sets the level * to {@code null}. The root logger's level is set to {@code Level.INFO}. * * @apiNote Calling this method also clears the LogManager {@linkplain * #getProperty(java.lang.String) properties}. The {@link * #updateConfiguration(java.util.function.Function) * updateConfiguration(Function)} or * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function) * updateConfiguration(InputStream, Function)} method can be used to * properly update to a new configuration. * * @throws SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). */ public void reset() throws SecurityException { checkPermission(); List persistent; // We don't want reset() and readConfiguration() // to run in parallel configurationLock.lock(); try { // install new empty properties props = new Properties(); // make sure we keep the loggers persistent until reset is done. // Those are the loggers for which we previously created a // handler from the configuration, and we need to prevent them // from being gc'ed until those handlers are closed. persistent = new ArrayList<>(closeOnResetLoggers); closeOnResetLoggers.clear(); // if reset has been called from shutdown-hook (Cleaner), // or if reset has been called from readConfiguration() which // already holds the lock and will change the state itself, // then do not change state here... if (globalHandlersState != STATE_SHUTDOWN && globalHandlersState != STATE_READING_CONFIG) { // ...else user called reset()... // Since we are doing a reset we no longer want to initialize // the global handlers, if they haven't been initialized yet. globalHandlersState = STATE_INITIALIZED; } for (LoggerContext cx : contexts()) { resetLoggerContext(cx); } persistent.clear(); } finally { configurationLock.unlock(); } } private void resetLoggerContext(LoggerContext cx) { Enumeration enum_ = cx.getLoggerNames(); while (enum_.hasMoreElements()) { String name = enum_.nextElement(); Logger logger = cx.findLogger(name); if (logger != null) { resetLogger(logger); } } } private void closeHandlers(Logger logger) { Handler[] targets = logger.getHandlers(); for (Handler h : targets) { logger.removeHandler(h); try { h.close(); } catch (Exception ex) { // Problems closing a handler? Keep going... } catch (Error e) { // ignore Errors while shutting down if (globalHandlersState != STATE_SHUTDOWN) { throw e; } } } } // Private method to reset an individual target logger. private void resetLogger(Logger logger) { // Close all the Logger handlers. closeHandlers(logger); // Reset Logger level 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 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()]); } /** * Reads and initializes the logging configuration from the given input stream. * *

* Any {@linkplain #addConfigurationListener registered configuration * listener} will be invoked after the properties are read. * * @apiNote This {@code readConfiguration} method should only be used for * initializing the configuration during LogManager initialization or * used with the "java.util.logging.config.class" property. * When this method is called after loggers have been created, all * existing loggers will be {@linkplain #reset() reset}. Then any * existing loggers that have a level property specified in the * given input stream will be {@linkplain * Logger#setLevel(java.util.logging.Level) set} to the specified log level. *

* To properly update the logging configuration, use the * {@link #updateConfiguration(java.util.function.Function)} or * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)} * method instead. * * @param ins stream to read properties from * @throws SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"). * @throws IOException if there are problems reading from the stream, * or the given stream is not in the * {@linkplain java.util.Properties properties file} format. */ public void readConfiguration(InputStream ins) throws IOException, SecurityException { checkPermission(); // We don't want reset() and readConfiguration() to run // in parallel. configurationLock.lock(); try { if (globalHandlersState == STATE_SHUTDOWN) { // already in terminal state: don't even bother // to read the configuration return; } // change state to STATE_READING_CONFIG to signal reset() to not change it globalHandlersState = STATE_READING_CONFIG; try { // reset configuration which leaves globalHandlersState at STATE_READING_CONFIG // so that while reading configuration, any ongoing logging requests block and // wait for the outcome (see the end of this try statement) reset(); try { // Load the properties props.load(ins); } catch (IllegalArgumentException x) { // props.load may throw an IllegalArgumentException if the stream // contains malformed Unicode escape sequences. // We wrap that in an IOException as readConfiguration is // specified to throw IOException if there are problems reading // from the stream. // Note: new IOException(x.getMessage(), x) allow us to get a more // concise error message than new IOException(x); throw new IOException(x.getMessage(), x); } // Instantiate new configuration objects. String names[] = parseClassNames("config"); for (String word : names) { try { Class clz = ClassLoader.getSystemClassLoader().loadClass(word); @SuppressWarnings("deprecation") Object witness = clz.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(); // Note that we need to reinitialize global handles when // they are first referenced. globalHandlersState = STATE_UNINITIALIZED; } catch (Throwable t) { // If there were any trouble, then set state to STATE_INITIALIZED // so that no global handlers reinitialization is performed on not fully // initialized configuration. globalHandlersState = STATE_INITIALIZED; // re-throw throw t; } } finally { configurationLock.unlock(); } // should be called out of lock to avoid dead-lock situations // when user code is involved invokeConfigurationListeners(); } // This enum enumerate the configuration properties that will be // updated on existing loggers when the configuration is updated // with LogManager.updateConfiguration(). // // Note that this works properly only for the global LogManager - as // Handler and its subclasses get their configuration from // LogManager.getLogManager(). // static enum ConfigProperty { LEVEL(".level"), HANDLERS(".handlers"), USEPARENT(".useParentHandlers"); final String suffix; final int length; private ConfigProperty(String suffix) { this.suffix = Objects.requireNonNull(suffix); length = suffix.length(); } public boolean handleKey(String key) { if (this == HANDLERS && suffix.substring(1).equals(key)) return true; if (this == HANDLERS && suffix.equals(key)) return false; return key.endsWith(suffix); } String key(String loggerName) { if (this == HANDLERS && (loggerName == null || loggerName.isEmpty())) { return suffix.substring(1); } return loggerName + suffix; } String loggerName(String key) { assert key.equals(suffix.substring(1)) && this == HANDLERS || key.endsWith(suffix); if (this == HANDLERS && suffix.substring(1).equals(key)) return ""; return key.substring(0, key.length() - length); } /** * If the property is one that should be updated on existing loggers by * updateConfiguration, returns the name of the logger for which the * property is configured. Otherwise, returns null. * @param property a property key in 'props' * @return the name of the logger on which the property is to be set, * if the property is one that should be updated on existing * loggers, {@code null} otherwise. */ static String getLoggerName(String property) { for (ConfigProperty p : ConfigProperty.ALL) { if (p.handleKey(property)) { return p.loggerName(property); } } return null; // Not a property that should be updated. } /** * Find the ConfigProperty corresponding to the given * property key (may find none). * @param property a property key in 'props' * @return An optional containing a ConfigProperty object, * if the property is one that should be updated on existing * loggers, empty otherwise. */ static Optional find(String property) { return ConfigProperty.ALL.stream() .filter(p -> p.handleKey(property)) .findFirst(); } /** * Returns true if the given property is one that should be updated * on existing loggers. * Used to filter property name streams. * @param property a property key from the configuration. * @return true if this property is of interest for updateConfiguration. */ static boolean matches(String property) { return find(property).isPresent(); } /** * Returns true if the new property value is different from the old, * and therefore needs to be updated on existing loggers. * @param k a property key in the configuration * @param previous the old configuration * @param next the new configuration * @return true if the property is changing value between the two * configurations. */ static boolean needsUpdating(String k, Properties previous, Properties next) { final String p = trim(previous.getProperty(k, null)); final String n = trim(next.getProperty(k, null)); return ! Objects.equals(p,n); } /** * Applies the mapping function for the given key to the next * configuration. * If the mapping function is null then this method does nothing. * Otherwise, it calls the mapping function to compute the value * that should be associated with {@code key} in the resulting * configuration, and applies it to {@code next}. * If the mapping function returns {@code null} the key is removed * from {@code next}. * * @param k a property key in the configuration * @param previous the old configuration * @param next the new configuration (modified by this function) * @param mappingFunction the mapping function. */ static void merge(String k, Properties previous, Properties next, BiFunction mappingFunction) { String p = trim(previous.getProperty(k, null)); String n = trim(next.getProperty(k, null)); String mapped = trim(mappingFunction.apply(p,n)); if (!Objects.equals(n, mapped)) { if (mapped == null) { next.remove(k); } else { next.setProperty(k, mapped); } } } private static final EnumSet ALL = EnumSet.allOf(ConfigProperty.class); } // trim the value if not null. private static String trim(String value) { return value == null ? null : value.trim(); } /** * An object that keep track of loggers we have already visited. * Used when updating configuration, to avoid processing the same logger * twice. */ static final class VisitedLoggers implements Predicate { final IdentityHashMap visited; private VisitedLoggers(IdentityHashMap visited) { this.visited = visited; } VisitedLoggers() { this(new IdentityHashMap<>()); } @Override public boolean test(Logger logger) { return visited != null && visited.put(logger, Boolean.TRUE) != null; } public void clear() { if (visited != null) visited.clear(); } // An object that considers that no logger has ever been visited. // This is used when processParentHandlers is called from // LoggerContext.addLocalLogger static final VisitedLoggers NEVER = new VisitedLoggers(null); } /** * Type of the modification for a given property. One of SAME, ADDED, CHANGED, * or REMOVED. */ static enum ModType { SAME, // property had no value in the old and new conf, or had the // same value in both. ADDED, // property had no value in the old conf, but has one in the new. CHANGED, // property has a different value in the old conf and the new conf. REMOVED; // property has no value in the new conf, but had one in the old. static ModType of(String previous, String next) { if (previous == null && next != null) { return ADDED; } if (next == null && previous != null) { return REMOVED; } if (!Objects.equals(trim(previous), trim(next))) { return CHANGED; } return SAME; } } /** * Updates the logging configuration. *

* If the "java.util.logging.config.file" system property is set, * then the property value specifies the properties file to be read * as the new configuration. Otherwise, the LogManager default * configuration is used. *
The default configuration is typically loaded from the * properties file "{@code conf/logging.properties}" in the * Java installation directory. *

* This method reads the new configuration and calls the {@link * #updateConfiguration(java.io.InputStream, java.util.function.Function) * updateConfiguration(ins, mapper)} method to * update the configuration. * * @apiNote * This method updates the logging configuration from reading * a properties file and ignores the "java.util.logging.config.class" * system property. The "java.util.logging.config.class" property is * only used by the {@link #readConfiguration()} method to load a custom * configuration class as an initial configuration. * * @param mapper a functional interface that takes a configuration * key k and returns a function f(o,n) whose returned * value will be applied to the resulting configuration. The * function f may return {@code null} to indicate that the property * k will not be added to the resulting configuration. *
* If {@code mapper} is {@code null} then {@code (k) -> ((o, n) -> n)} is * assumed. *
* For each k, the mapped function f will * be invoked with the value associated with k in the old * configuration (i.e o) and the value associated with * k in the new configuration (i.e. n). *
A {@code null} value for o or n indicates that no * value was present for k in the corresponding configuration. * * @throws SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"), or * does not have the permissions required to set up the * configuration (e.g. open file specified for FileHandlers * etc...) * * @throws NullPointerException if {@code mapper} returns a {@code null} * function when invoked. * * @throws IOException if there are problems reading from the * logging configuration file. * * @see #updateConfiguration(java.io.InputStream, java.util.function.Function) * @since 9 */ public void updateConfiguration(Function> mapper) throws IOException { checkPermission(); ensureLogManagerInitialized(); drainLoggerRefQueueBounded(); String fname = getConfigurationFileName(); try (final InputStream in = new FileInputStream(fname)) { final BufferedInputStream bin = new BufferedInputStream(in); updateConfiguration(bin, mapper); } } /** * Updates the logging configuration. *

* For each configuration key in the {@linkplain * #getProperty(java.lang.String) existing configuration} and * the given input stream configuration, the given {@code mapper} function * is invoked to map from the configuration key to a function, * f(o,n), that takes the old value and new value and returns * the resulting value to be applied in the resulting configuration, * as specified in the table below. *

Let k be a configuration key in the old or new configuration, * o be the old value (i.e. the value associated * with k in the old configuration), n be the * new value (i.e. the value associated with k in the new * configuration), and f be the function returned * by {@code mapper.apply(}k{@code )}: then v = f(o,n) is the * resulting value. If v is not {@code null}, then a property * k with value v will be added to the resulting configuration. * Otherwise, it will be omitted. *
A {@code null} value may be passed to function * f to indicate that the corresponding configuration has no * configuration key k. * The function f may return {@code null} to indicate that * there will be no value associated with k in the resulting * configuration. *

* If {@code mapper} is {@code null}, then v will be set to * n. *

* LogManager {@linkplain #getProperty(java.lang.String) properties} are * updated with the resulting value in the resulting configuration. *

* The registered {@linkplain #addConfigurationListener configuration * listeners} will be invoked after the configuration is successfully updated. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Updating configuration properties
PropertyResulting Behavior
{@code .level} *
    *
  • If the resulting configuration defines a level for a logger and * if the resulting level is different than the level specified in the * the old configuration, or not specified in * the old configuration, then if the logger exists or if children for * that logger exist, the level for that logger will be updated, * and the change propagated to any existing logger children. * This may cause the logger to be created, if necessary. *
  • *
  • If the old configuration defined a level for a logger, and the * resulting configuration doesn't, then this change will not be * propagated to existing loggers, if any. * To completely replace a configuration - the caller should therefore * call {@link #reset() reset} to empty the current configuration, * before calling {@code updateConfiguration}. *
  • *
*
{@code .useParentHandlers} *
    *
  • If either the resulting or the old value for the useParentHandlers * property is not null, then if the logger exists or if children for * that logger exist, that logger will be updated to the resulting * value. * The value of the useParentHandlers property is the value specified * in the configuration; if not specified, the default is true. *
  • *
*
{@code .handlers} *
    *
  • If the resulting configuration defines a list of handlers for a * logger, and if the resulting list is different than the list * specified in the old configuration for that logger (that could be * empty), then if the logger exists or its children exist, the * handlers associated with that logger are closed and removed and * the new handlers will be created per the resulting configuration * and added to that logger, creating that logger if necessary. *
  • *
  • If the old configuration defined some handlers for a logger, and * the resulting configuration doesn't, if that logger exists, * its handlers will be removed and closed. *
  • *
  • Changing the list of handlers on an existing logger will cause all * its previous handlers to be removed and closed, regardless of whether * they had been created from the configuration or programmatically. * The old handlers will be replaced by new handlers, if any. *
  • *
*
{@code .*} *
    *
  • Properties configured/changed on handler classes will only affect * newly created handlers. If a node is configured with the same list * of handlers in the old and the resulting configuration, then these * handlers will remain unchanged. *
  • *
*
{@code config} and any other property *
    *
  • The resulting value for these property will be stored in the * LogManager properties, but {@code updateConfiguration} will not parse * or process their values. *
  • *
*
*

* Example mapper functions: *

*

    *
  • Replace all logging properties with the new configuration: *

    {@code (k) -> ((o, n) -> n)}: *

    this is equivalent to passing a null {@code mapper} parameter. *
  • *
  • Merge the new configuration and old configuration and use the * new value if k exists in the new configuration: *

    {@code (k) -> ((o, n) -> n == null ? o : n)}: *

    as if merging two collections as follows: * {@code result.putAll(oldc); result.putAll(newc)}.
  • *
  • Merge the new configuration and old configuration and use the old * value if k exists in the old configuration: *

    {@code (k) -> ((o, n) -> o == null ? n : o)}: *

    as if merging two collections as follows: * {@code result.putAll(newc); result.putAll(oldc)}.
  • *
  • Replace all properties with the new configuration except the handler * property to configure Logger's handler that is not root logger: *
    *
    {@code (k) -> k.endsWith(".handlers")}
         *      {@code     ? ((o, n) -> (o == null ? n : o))}
         *      {@code     : ((o, n) -> n)}
    *
  • *
*

* To completely reinitialize a configuration, an application can first call * {@link #reset() reset} to fully remove the old configuration, followed by * {@code updateConfiguration} to initialize the new configuration. * * @param ins a stream to read properties from * @param mapper a functional interface that takes a configuration * key k and returns a function f(o,n) whose returned * value will be applied to the resulting configuration. The * function f may return {@code null} to indicate that the property * k will not be added to the resulting configuration. *
* If {@code mapper} is {@code null} then {@code (k) -> ((o, n) -> n)} is * assumed. *
* For each k, the mapped function f will * be invoked with the value associated with k in the old * configuration (i.e o) and the value associated with * k in the new configuration (i.e. n). *
A {@code null} value for o or n indicates that no * value was present for k in the corresponding configuration. * * @throws SecurityException if a security manager exists and if * the caller does not have LoggingPermission("control"), or * does not have the permissions required to set up the * configuration (e.g. open files specified for FileHandlers) * * @throws NullPointerException if {@code ins} is null or if * {@code mapper} returns a null function when invoked. * * @throws IOException if there are problems reading from the stream, * or the given stream is not in the * {@linkplain java.util.Properties properties file} format. * @since 9 */ public void updateConfiguration(InputStream ins, Function> mapper) throws IOException { checkPermission(); ensureLogManagerInitialized(); drainLoggerRefQueueBounded(); final Properties previous; final Set updatePropertyNames; List cxs = Collections.emptyList(); final VisitedLoggers visited = new VisitedLoggers(); final Properties next = new Properties(); try { // Load the properties next.load(ins); } catch (IllegalArgumentException x) { // props.load may throw an IllegalArgumentException if the stream // contains malformed Unicode escape sequences. // We wrap that in an IOException as updateConfiguration is // specified to throw IOException if there are problems reading // from the stream. // Note: new IOException(x.getMessage(), x) allow us to get a more // concise error message than new IOException(x); throw new IOException(x.getMessage(), x); } if (globalHandlersState == STATE_SHUTDOWN) return; // exclusive lock: readConfiguration/reset/updateConfiguration can't // run concurrently. // configurationLock.writeLock().lock(); configurationLock.lock(); try { if (globalHandlersState == STATE_SHUTDOWN) return; previous = props; // Builds a TreeSet of all (old and new) property names. updatePropertyNames = Stream.concat(previous.stringPropertyNames().stream(), next.stringPropertyNames().stream()) .collect(Collectors.toCollection(TreeSet::new)); if (mapper != null) { // mapper will potentially modify the content of // 'next', so we need to call it before affecting props=next. // give a chance to the mapper to control all // properties - not just those we will reset. updatePropertyNames.stream() .forEachOrdered(k -> ConfigProperty .merge(k, previous, next, Objects.requireNonNull(mapper.apply(k)))); } props = next; // allKeys will contain all keys: // - which correspond to a configuration property we are interested in // (first filter) // - whose value needs to be updated (because it's new, removed, or // different) in the resulting configuration (second filter) final Stream allKeys = updatePropertyNames.stream() .filter(ConfigProperty::matches) .filter(k -> ConfigProperty.needsUpdating(k, previous, next)); // Group configuration properties by logger name // We use a TreeMap so that parent loggers will be visited before // child loggers. final Map> loggerConfigs = allKeys.collect(Collectors.groupingBy(ConfigProperty::getLoggerName, TreeMap::new, Collectors.toCollection(TreeSet::new))); if (!loggerConfigs.isEmpty()) { cxs = contexts(); } final List loggers = cxs.isEmpty() ? Collections.emptyList() : new ArrayList<>(cxs.size()); for (Map.Entry> e : loggerConfigs.entrySet()) { // This can be a logger name, or something else... // The only thing we know is that we found a property // we are interested in. // For instance, if we found x.y.z.level, then x.y.z could be // a logger, but it could also be a handler class... // Anyway... final String name = e.getKey(); final Set properties = e.getValue(); loggers.clear(); for (LoggerContext cx : cxs) { Logger l = cx.findLogger(name); if (l != null && !visited.test(l)) { loggers.add(l); } } if (loggers.isEmpty()) continue; for (String pk : properties) { ConfigProperty cp = ConfigProperty.find(pk).get(); String p = previous.getProperty(pk, null); String n = next.getProperty(pk, null); // Determines the type of modification. ModType mod = ModType.of(p, n); // mod == SAME means that the two values are equals, there // is nothing to do. Usually, this should not happen as such // properties should have been filtered above. // It could happen however if the properties had // trailing/leading whitespaces. if (mod == ModType.SAME) continue; switch (cp) { case LEVEL: if (mod == ModType.REMOVED) continue; Level level = Level.findLevel(trim(n)); if (level != null) { if (name.isEmpty()) { rootLogger.setLevel(level); } for (Logger l : loggers) { if (!name.isEmpty() || l != rootLogger) { l.setLevel(level); } } } break; case USEPARENT: if (!name.isEmpty()) { boolean useParent = getBooleanProperty(pk, true); if (n != null || p != null) { // reset the flag only if the previous value // or the new value are not null. for (Logger l : loggers) { l.setUseParentHandlers(useParent); } } } break; case HANDLERS: List hdls = null; if (name.isEmpty()) { // special handling for the root logger. globalHandlersState = STATE_READING_CONFIG; try { closeHandlers(rootLogger); globalHandlersState = STATE_UNINITIALIZED; } catch (Throwable t) { globalHandlersState = STATE_INITIALIZED; throw t; } } for (Logger l : loggers) { if (l == rootLogger) continue; closeHandlers(l); if (mod == ModType.REMOVED) { closeOnResetLoggers.removeIf(c -> c.logger == l); continue; } if (hdls == null) { hdls = name.isEmpty() ? Arrays.asList(rootLogger.getHandlers()) : createLoggerHandlers(name, pk); } setLoggerHandlers(l, name, pk, hdls); } break; default: break; } } } } finally { configurationLock.unlock(); visited.clear(); } // Now ensure that if an existing logger has acquired a new parent // in the configuration, this new parent will be created - if needed, // and added to the context of the existing child. // drainLoggerRefQueueBounded(); for (LoggerContext cx : cxs) { for (Enumeration names = cx.getLoggerNames() ; names.hasMoreElements();) { String name = names.nextElement(); if (name.isEmpty()) continue; // don't need to process parents on root. Logger l = cx.findLogger(name); if (l != null && !visited.test(l)) { // should pass visited here to cut the processing when // reaching a logger already visited. cx.processParentHandlers(l, name, visited); } } } // We changed the configuration: invoke configuration listeners invokeConfigurationListeners(); } /** * 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 long property. // If the property is not defined or cannot be parsed // we return the given default value. long getLongProperty(String name, long defaultValue) { String val = getProperty(name); if (val == null) { return defaultValue; } try { return Long.parseLong(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) { @SuppressWarnings("deprecation") Object o = ClassLoader.getSystemClassLoader().loadClass(val).newInstance(); return (Filter) o; } } 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) { @SuppressWarnings("deprecation") Object o = ClassLoader.getSystemClassLoader().loadClass(val).newInstance(); return (Formatter) o; } } 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 void initializeGlobalHandlers() { int state = globalHandlersState; if (state == STATE_INITIALIZED || state == STATE_SHUTDOWN) { // Nothing to do: return. return; } // If we have not initialized global handlers yet (or need to // reinitialize them), lets do it now (this case is indicated by // globalHandlersState == STATE_UNINITIALIZED). // If we are in the process of initializing global handlers we // also need to lock & wait (this case is indicated by // globalHandlersState == STATE_INITIALIZING). // If we are in the process of reading configuration we also need to // wait to see what the outcome will be (this case // is indicated by globalHandlersState == STATE_READING_CONFIG) // So in either case we need to wait for the lock. configurationLock.lock(); try { if (globalHandlersState != STATE_UNINITIALIZED) { return; // recursive call or nothing to do } // set globalHandlersState to STATE_INITIALIZING first to avoid // getting an infinite recursion when loadLoggerHandlers(...) // is going to call addHandler(...) globalHandlersState = STATE_INITIALIZING; try { loadLoggerHandlers(rootLogger, null, "handlers"); } finally { globalHandlersState = STATE_INITIALIZED; } } finally { configurationLock.unlock(); } } static 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"). *

* 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 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; } for (LogNode node : children.values()) { 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. 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); } } } /** * String representation of the * {@link javax.management.ObjectName} for the management interface * for the logging facility. * * @see java.lang.management.PlatformLoggingMXBean * * @since 1.5 */ public final static String LOGGING_MXBEAN_NAME = "java.util.logging:type=Logging"; /** * Returns {@code LoggingMXBean} for managing loggers. * * @return a {@link LoggingMXBean} object. * * @deprecated {@code java.util.logging.LoggingMXBean} is deprecated and * replaced with {@code java.lang.management.PlatformLoggingMXBean}. Use * {@link java.lang.management.ManagementFactory#getPlatformMXBean(Class) * ManagementFactory.getPlatformMXBean}(PlatformLoggingMXBean.class) * instead. * * @see java.lang.management.PlatformLoggingMXBean * @since 1.5 */ @Deprecated(since="9") public static synchronized LoggingMXBean getLoggingMXBean() { return Logging.getInstance(); } /** * Adds a configuration listener to be invoked each time the logging * configuration is read. * If the listener is already registered the method does nothing. *

* The listener is invoked with privileges that are restricted by the * calling context of this method. * The order in which the listeners are invoked is unspecified. *

* It is recommended that listeners do not throw errors or exceptions. * * If a listener terminates with an uncaught error or exception then * the first exception will be propagated to the caller of * {@link #readConfiguration()} (or {@link #readConfiguration(java.io.InputStream)}) * after all listeners have been invoked. * * @implNote If more than one listener terminates with an uncaught error or * exception, an implementation may record the additional errors or * exceptions as {@linkplain Throwable#addSuppressed(java.lang.Throwable) * suppressed exceptions}. * * @param listener A configuration listener that will be invoked after the * configuration changed. * @return This LogManager. * @throws SecurityException if a security manager exists and if the * caller does not have LoggingPermission("control"). * @throws NullPointerException if the listener is null. * * @since 9 */ public LogManager addConfigurationListener(Runnable listener) { final Runnable r = Objects.requireNonNull(listener); checkPermission(); final SecurityManager sm = System.getSecurityManager(); final AccessControlContext acc = sm == null ? null : AccessController.getContext(); final PrivilegedAction pa = acc == null ? null : () -> { r.run() ; return null; }; final Runnable pr = acc == null ? r : () -> AccessController.doPrivileged(pa, acc); // Will do nothing if already registered. listeners.putIfAbsent(r, pr); return this; } /** * Removes a previously registered configuration listener. * * Returns silently if the listener is not found. * * @param listener the configuration listener to remove. * @throws NullPointerException if the listener is null. * @throws SecurityException if a security manager exists and if the * caller does not have LoggingPermission("control"). * * @since 9 */ public void removeConfigurationListener(Runnable listener) { final Runnable key = Objects.requireNonNull(listener); checkPermission(); listeners.remove(key); } private void invokeConfigurationListeners() { Throwable t = null; // We're using an IdentityHashMap because we want to compare // keys using identity (==). // We don't want to loop within a block synchronized on 'listeners' // to avoid invoking listeners from yet another synchronized block. // So we're taking a snapshot of the values list to avoid the risk of // ConcurrentModificationException while looping. // for (Runnable c : listeners.values().toArray(new Runnable[0])) { try { c.run(); } catch (ThreadDeath death) { throw death; } catch (Error | RuntimeException x) { if (t == null) t = x; else t.addSuppressed(x); } } // Listeners are not supposed to throw exceptions, but if that // happens, we will rethrow the first error or exception that is raised // after all listeners have been invoked. if (t instanceof Error) throw (Error)t; if (t instanceof RuntimeException) throw (RuntimeException)t; } /** * This class allows the {@link LoggingProviderImpl} to demand loggers on * behalf of system and application classes. */ private static final class LoggingProviderAccess implements LoggingProviderImpl.LogManagerAccess, PrivilegedAction { private LoggingProviderAccess() { } /** * Demands a logger on behalf of the given {@code module}. *

* If a named logger suitable for the given module is found * returns it. * Otherwise, creates a new logger suitable for the given module. * * @param name The logger name. * @param module The module on which behalf the logger is created/retrieved. * @return A logger for the given {@code module}. * * @throws NullPointerException if {@code name} is {@code null} * or {@code module} is {@code null}. * @throws IllegalArgumentException if {@code manager} is not the default * LogManager. * @throws SecurityException if a security manager is present and the * calling code doesn't have the * {@link LoggingPermission LoggingPermission("demandLogger", null)}. */ @Override public Logger demandLoggerFor(LogManager manager, String name, Module module) { if (manager != getLogManager()) { // having LogManager as parameter just ensures that the // caller will have initialized the LogManager before reaching // here. throw new IllegalArgumentException("manager"); } Objects.requireNonNull(name); Objects.requireNonNull(module); SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(controlPermission); } if (isSystem(module)) { return manager.demandSystemLogger(name, Logger.SYSTEM_LOGGER_RB_NAME, module); } else { return manager.demandLogger(name, null, module); } } @Override public Void run() { LoggingProviderImpl.setLogManagerAccess(INSTANCE); return null; } static final LoggingProviderAccess INSTANCE = new LoggingProviderAccess(); } static { AccessController.doPrivileged(LoggingProviderAccess.INSTANCE, null, controlPermission); } }