|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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.access.JavaAWTAccess; |
|
import jdk.internal.access.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. |
|
* <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. |
|
* |
|
* <h2>LogManager Configuration</h2> |
|
* |
|
* 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. |
|
* <p> |
|
* The LogManager defines two optional system properties that allow control over |
|
* the initial configuration, as specified in the {@link #readConfiguration()} |
|
* method: |
|
* <ul> |
|
* <li>{@systemProperty java.util.logging.config.class} |
|
* <li>{@systemProperty java.util.logging.config.file} |
|
* </ul> |
|
* <p> |
|
* These two system properties may be specified on the command line to the "java" |
|
* command, or as system property definitions passed to JNI_CreateJavaVM. |
|
* <p> |
|
* The {@linkplain Properties properties} for loggers and Handlers will have |
|
* names starting with the dot-separated name for the handler or logger.<br> |
|
* 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>.handlers.ensureCloseOnReset". This defines a |
|
* a boolean value. If "<logger>.handlers" is not defined or is empty, |
|
* this property is ignored. Otherwise it defaults to {@code true}. When the |
|
* value is {@code true}, the handlers associated with the logger are guaranteed |
|
* to be closed on {@linkplain #reset} and shutdown. This can be turned off |
|
* by explicitly setting "<logger>.handlers.ensureCloseOnReset=false" in |
|
* the configuration. Note that turning this property off causes the risk of |
|
* introducing a resource leak, as the logger may get garbage collected before |
|
* {@code reset()} is called, thus preventing its handlers from being closed |
|
* on {@code reset()}. In that case it is the responsibility of the application |
|
* to ensure that the handlers are closed before the logger is garbage |
|
* collected. |
|
* |
|
* <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 { |
|
|
|
// '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 |
|
|
|
private volatile Properties props = new Properties(); |
|
private static final Level defaultLevel = Level.INFO; |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
private volatile boolean readPrimordialConfiguration; |
|
// Have we initialized global (root) handlers yet? |
|
|
|
private static final int |
|
STATE_INITIALIZED = 0, |
|
STATE_INITIALIZING = 1, |
|
STATE_READING_CONFIG = 2, |
|
STATE_UNINITIALIZED = 3, |
|
STATE_SHUTDOWN = 4; |
|
private volatile int globalHandlersState; |
|
|
|
private final ReentrantLock configurationLock = new ReentrantLock(); |
|
|
|
// This list contains the loggers for which some handlers have been |
|
// explicitly configured in the configuration file. |
|
|
|
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<CloseOnReset> closeOnResetLoggers = |
|
new CopyOnWriteArrayList<>(); |
|
|
|
|
|
private final Map<Object, Runnable> listeners = |
|
Collections.synchronizedMap(new IdentityHashMap<>()); |
|
|
|
|
|
@SuppressWarnings("removal") |
|
private static final LogManager 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) { |
|
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. |
|
|
|
private class Cleaner extends Thread { |
|
|
|
private Cleaner() { |
|
super(null, null, "Logging-Cleaner", 0, false); |
|
|
|
|
|
*/ |
|
this.setContextClassLoader(null); |
|
} |
|
|
|
@Override |
|
public void run() { |
|
// This is to ensure the LogManager.<clinit> is completed |
|
|
|
LogManager mgr = manager; |
|
|
|
// set globalHandlersState to STATE_SHUTDOWN atomically so that |
|
// no attempts are made to (re)initialize the handlers or (re)read |
|
|
|
configurationLock.lock(); |
|
globalHandlersState = STATE_SHUTDOWN; |
|
configurationLock.unlock(); |
|
|
|
|
|
reset(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected LogManager() { |
|
this(checkSubclassPermissions()); |
|
} |
|
|
|
private LogManager(Void checked) { |
|
|
|
|
|
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() { |
|
@SuppressWarnings("removal") |
|
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 |
|
|
|
sm.checkPermission(new RuntimePermission("shutdownHooks")); |
|
sm.checkPermission(new RuntimePermission("setContextClassLoader")); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private boolean initializedCalled = false; |
|
private volatile boolean initializationDone = false; |
|
@SuppressWarnings("removal") |
|
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 |
|
|
|
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. |
|
|
|
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 |
|
|
|
return; |
|
} |
|
// Calling addLogger below will in turn call requiresDefaultLogger() |
|
// which will call ensureLogManagerInitialized(). |
|
|
|
initializedCalled = true; |
|
try { |
|
AccessController.doPrivileged(new PrivilegedAction<Object>() { |
|
@Override |
|
public Object run() { |
|
assert rootLogger == null; |
|
assert initializedCalled && !initializationDone; |
|
|
|
// create root logger before reading primordial |
|
// configuration - to ensure that it will be added |
|
|
|
final Logger root = owner.rootLogger = owner.new RootLogger(); |
|
|
|
|
|
owner.readPrimordialConfiguration(); |
|
|
|
|
|
owner.addLogger(root); |
|
|
|
|
|
if (!root.isLevelInitialized()) { |
|
root.setLevel(defaultLevel); |
|
} |
|
|
|
// Adding the global Logger. |
|
// Do not call Logger.getGlobal() here as this might trigger |
|
|
|
@SuppressWarnings("deprecation") |
|
final Logger global = Logger.global; |
|
|
|
// Make sure the global logger will be registered in the |
|
|
|
owner.addLogger(global); |
|
return null; |
|
} |
|
}); |
|
} finally { |
|
initializationDone = true; |
|
} |
|
} finally { |
|
configurationLock.unlock(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static LogManager getLogManager() { |
|
if (manager != null) { |
|
manager.ensureLogManagerInitialized(); |
|
} |
|
return manager; |
|
} |
|
|
|
private void readPrimordialConfiguration() { |
|
if (!readPrimordialConfiguration) { |
|
// If System.in/out/err are null, it's a good |
|
// indication that we're still in the |
|
|
|
if (System.out == null) { |
|
return; |
|
} |
|
readPrimordialConfiguration = true; |
|
try { |
|
readConfiguration(); |
|
|
|
|
|
jdk.internal.logger.BootstrapLogger.redirectTemporaryLoggers(); |
|
|
|
} catch (Exception ex) { |
|
assert false : "Exception raised while reading logging configuration: " + ex; |
|
} |
|
} |
|
} |
|
|
|
|
|
private WeakHashMap<Object, LoggerContext> contextsMap = null; |
|
|
|
// Returns the LoggerContext for the user code (i.e. application or AppContext). |
|
|
|
private LoggerContext getUserContext() { |
|
LoggerContext context = null; |
|
|
|
@SuppressWarnings("removal") |
|
SecurityManager sm = System.getSecurityManager(); |
|
JavaAWTAccess javaAwtAccess = SharedSecrets.getJavaAWTAccess(); |
|
if (sm != null && javaAwtAccess != null) { |
|
|
|
final Object ecx = javaAwtAccess.getAppletContext(); |
|
if (ecx != null) { |
|
synchronized (javaAwtAccess) { |
|
// find the AppContext of the applet code |
|
|
|
if (contextsMap == null) { |
|
contextsMap = new WeakHashMap<>(); |
|
} |
|
context = contextsMap.get(ecx); |
|
if (context == null) { |
|
|
|
context = new LoggerContext(); |
|
contextsMap.put(ecx, context); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return context != null ? context : userContext; |
|
} |
|
|
|
|
|
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, |
|
|
|
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) { |
|
|
|
Logger newLogger = new Logger(name, resourceBundleName, |
|
module, this, false); |
|
do { |
|
if (addLogger(newLogger)) { |
|
// We successfully added the new Logger that we |
|
|
|
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 |
|
|
|
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); |
|
} |
|
|
|
@SuppressWarnings("removal") |
|
Logger demandSystemLogger(String name, String resourceBundleName, Module module) { |
|
|
|
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 |
|
|
|
Logger logger; |
|
do { |
|
// First attempt to call addLogger instead of getLogger |
|
// This would avoid potential bug in custom LogManager.getLogger |
|
|
|
if (addLogger(sysLogger)) { |
|
|
|
logger = sysLogger; |
|
} else { |
|
logger = getLogger(name); |
|
} |
|
} while (logger == null); |
|
|
|
|
|
if (logger != sysLogger) { |
|
|
|
final Logger l = logger; |
|
AccessController.doPrivileged(new PrivilegedAction<Void>() { |
|
@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 |
|
|
|
class LoggerContext { |
|
|
|
private final ConcurrentHashMap<String,LoggerWeakRef> namedLoggers = |
|
new ConcurrentHashMap<>(); |
|
|
|
private final LogNode root; |
|
private LoggerContext() { |
|
this.root = new LogNode(null, this); |
|
} |
|
|
|
|
|
// Tells whether default loggers are required in this context. |
|
|
|
final boolean requiresDefaultLoggers() { |
|
final boolean requiresDefaultLoggers = (getOwner() == manager); |
|
if (requiresDefaultLoggers) { |
|
getOwner().ensureLogManagerInitialized(); |
|
} |
|
return requiresDefaultLoggers; |
|
} |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
final Logger getGlobalLogger() { |
|
@SuppressWarnings("deprecation") |
|
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 |
|
|
|
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()) { |
|
|
|
ensureDefaultLogger(getRootLogger()); |
|
ensureDefaultLogger(getGlobalLogger()); |
|
} |
|
} |
|
|
|
|
|
Logger findLogger(String name) { |
|
|
|
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 |
|
|
|
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 |
|
|
|
synchronized(this) { |
|
// ensure that this context is properly initialized before |
|
|
|
ensureInitialized(); |
|
ref = namedLoggers.get(name); |
|
if (ref == null) { |
|
return null; |
|
} |
|
logger = ref.get(); |
|
if (logger == null) { |
|
// The namedLoggers map holds stale weak reference |
|
|
|
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 |
|
|
|
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 |
|
|
|
assert logger == null; |
|
|
|
return; |
|
} |
|
|
|
|
|
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, |
|
|
|
addLocalLogger(logger, false); |
|
} |
|
} |
|
|
|
boolean addLocalLogger(Logger logger) { |
|
|
|
return addLocalLogger(logger, requiresDefaultLoggers()); |
|
} |
|
|
|
// Add a logger to this context. This method will only set its level |
|
|
|
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 |
|
|
|
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.refersTo(null)) { |
|
// It's possible that the Logger was GC'ed after a |
|
// drainLoggerRefQueueBounded() call above so allow |
|
|
|
ref.dispose(); |
|
} else { |
|
|
|
return false; |
|
} |
|
} |
|
|
|
// We're adding a new logger. |
|
|
|
final LogManager owner = getOwner(); |
|
logger.setLogManager(owner); |
|
ref = owner.new LoggerWeakRef(logger); |
|
|
|
// Apply any initial level defined for the new logger, unless |
|
|
|
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 |
|
|
|
processParentHandlers(logger, name, VisitedLoggers.NEVER); |
|
|
|
|
|
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); |
|
} |
|
|
|
node.walkAndSetParent(logger); |
|
|
|
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 |
|
|
|
namedLoggers.put(name, ref); |
|
return true; |
|
} |
|
|
|
void removeLoggerRef(String name, LoggerWeakRef ref) { |
|
namedLoggers.remove(name, ref); |
|
} |
|
|
|
synchronized Enumeration<String> getLoggerNames() { |
|
// ensure that this context is properly initialized before |
|
|
|
ensureInitialized(); |
|
return Collections.enumeration(namedLoggers.keySet()); |
|
} |
|
|
|
// If logger.getUseParentHandlers() returns 'true' and any of the logger's |
|
|
|
@SuppressWarnings("removal") |
|
private void processParentHandlers(final Logger logger, final String name, |
|
Predicate<Logger> visited) { |
|
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. |
|
|
|
if (visited.test(demandLogger(pname, null, null))) { |
|
break; |
|
} |
|
} |
|
ix = ix2+1; |
|
} |
|
} |
|
|
|
// Gets a node in our tree of logger nodes. |
|
|
|
LogNode getNode(String name) { |
|
if (name == null || name.isEmpty()) { |
|
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 |
|
|
|
@Override |
|
Logger demandLogger(String name, String resourceBundleName, |
|
Module module) { |
|
Logger result = findLogger(name); |
|
if (result == null) { |
|
|
|
Logger newLogger = new Logger(name, resourceBundleName, |
|
module, getOwner(), true); |
|
do { |
|
if (addLocalLogger(newLogger)) { |
|
// We successfully added the new Logger that we |
|
|
|
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 |
|
|
|
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 |
|
|
|
@SuppressWarnings("removal") |
|
private void loadLoggerHandlers(final Logger logger, final String name, |
|
final String handlersPropertyName) |
|
{ |
|
AccessController.doPrivileged(new PrivilegedAction<Void>() { |
|
@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<Handler> handlers) |
|
{ |
|
final boolean ensureCloseOnReset = ! handlers.isEmpty() |
|
&& getBooleanProperty(handlersPropertyName + ".ensureCloseOnReset",true); |
|
int count = 0; |
|
for (Handler hdl : handlers) { |
|
logger.addHandler(hdl); |
|
if (++count == 1 && ensureCloseOnReset) { |
|
|
|
closeOnResetLoggers.addIfAbsent(CloseOnReset.create(logger)); |
|
} |
|
} |
|
} |
|
|
|
private List<Handler> createLoggerHandlers(final String name, |
|
final String handlersPropertyName) |
|
{ |
|
String names[] = parseClassNames(handlersPropertyName); |
|
List<Handler> 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 |
|
|
|
String levs = getProperty(type + ".level"); |
|
if (levs != null) { |
|
Level l = Level.findLevel(levs); |
|
if (l != null) { |
|
hdl.setLevel(l); |
|
} else { |
|
|
|
System.err.println("Can't set level for " + type); |
|
} |
|
} |
|
|
|
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 |
|
|
|
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; |
|
private LogNode node; |
|
private WeakReference<Logger> parentRef; |
|
private boolean disposed = false; |
|
|
|
LoggerWeakRef(Logger logger) { |
|
super(logger, loggerRefQueue); |
|
|
|
name = logger.getName(); |
|
} |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
synchronized (n.context) { |
|
// if we have a LogNode, then we were a named Logger |
|
|
|
n.context.removeLoggerRef(name, this); |
|
name = null; |
|
|
|
// LogNode may have been reused - so only clear |
|
|
|
if (n.loggerRef == this) { |
|
n.loggerRef = null; |
|
} |
|
node = null; |
|
} |
|
} |
|
|
|
if (parentRef != null) { |
|
|
|
Logger parent = parentRef.get(); |
|
if (parent != null) { |
|
// the parent Logger is still there so clear the |
|
|
|
parent.removeChildLogger(this); |
|
} |
|
parentRef = null; |
|
} |
|
} |
|
|
|
|
|
void setNode(LogNode node) { |
|
this.node = node; |
|
} |
|
|
|
|
|
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 static final int MAX_ITERATIONS = 400; |
|
final void drainLoggerRefQueueBounded() { |
|
for (int i = 0; i < MAX_ITERATIONS; i++) { |
|
if (loggerRefQueue == null) { |
|
|
|
break; |
|
} |
|
|
|
LoggerWeakRef ref = (LoggerWeakRef) loggerRefQueue.poll(); |
|
if (ref == null) { |
|
break; |
|
} |
|
|
|
ref.dispose(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean addLogger(Logger logger) { |
|
final String name = logger.getName(); |
|
if (name == null) { |
|
throw new NullPointerException(); |
|
} |
|
drainLoggerRefQueueBounded(); |
|
LoggerContext cx = getUserContext(); |
|
if (cx.addLocalLogger(logger) || forceLoadHandlers(logger)) { |
|
// Do we have a per logger handler too? |
|
|
|
loadLoggerHandlers(logger, name, name + ".handlers"); |
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
// Checks whether the given logger is a special logger |
|
// that still requires handler initialization. |
|
// This method will only return true for the root and |
|
// global loggers and only if called by the thread that |
|
// performs initialization of the LogManager, during that |
|
|
|
@SuppressWarnings("deprecation") |
|
private boolean forceLoadHandlers(Logger logger) { |
|
// Called just after reading the primordial configuration, in |
|
// the same thread that reads it. |
|
// The root and global logger would already be present in the context |
|
// by this point, but we would not have called loadLoggerHandlers |
|
|
|
return (logger == rootLogger || logger == Logger.global) |
|
&& !initializationDone |
|
&& initializedCalled |
|
&& configurationLock.isHeldByCurrentThread(); |
|
} |
|
|
|
// Private method to set a level on a logger. |
|
|
|
@SuppressWarnings("removal") |
|
private static void doSetLevel(final Logger logger, final Level level) { |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm == null) { |
|
|
|
logger.setLevel(level); |
|
return; |
|
} |
|
// There is a security manager. Raise privilege before |
|
|
|
AccessController.doPrivileged(new PrivilegedAction<Object>() { |
|
@Override |
|
public Object run() { |
|
logger.setLevel(level); |
|
return null; |
|
}}); |
|
} |
|
|
|
// Private method to set a parent on a logger. |
|
|
|
@SuppressWarnings("removal") |
|
private static void doSetParent(final Logger logger, final Logger parent) { |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm == null) { |
|
|
|
logger.setParent(parent); |
|
return; |
|
} |
|
// There is a security manager. Raise privilege before |
|
|
|
AccessController.doPrivileged(new PrivilegedAction<Object>() { |
|
@Override |
|
public Object run() { |
|
logger.setParent(parent); |
|
return null; |
|
}}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Logger getLogger(String name) { |
|
return getUserContext().findLogger(name); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Enumeration<String> getLoggerNames() { |
|
return getUserContext().getLoggerNames(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void readConfiguration() throws IOException, SecurityException { |
|
checkPermission(); |
|
|
|
|
|
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 |
|
|
|
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. |
|
* <p> |
|
* 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<CloseOnReset> persistent; |
|
|
|
// We don't want reset() and readConfiguration() |
|
|
|
configurationLock.lock(); |
|
try { |
|
|
|
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 |
|
|
|
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, |
|
|
|
if (globalHandlersState != STATE_SHUTDOWN && |
|
globalHandlersState != STATE_READING_CONFIG) { |
|
// ...else user called reset()... |
|
// Since we are doing a reset we no longer want to initialize |
|
|
|
globalHandlersState = STATE_INITIALIZED; |
|
} |
|
|
|
for (LoggerContext cx : contexts()) { |
|
resetLoggerContext(cx); |
|
} |
|
|
|
persistent.clear(); |
|
} finally { |
|
configurationLock.unlock(); |
|
} |
|
} |
|
|
|
private void resetLoggerContext(LoggerContext cx) { |
|
Enumeration<String> 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) { |
|
|
|
if (globalHandlersState != STATE_SHUTDOWN) { |
|
throw e; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
private void resetLogger(Logger logger) { |
|
|
|
closeHandlers(logger); |
|
|
|
|
|
String name = logger.getName(); |
|
if (name != null && name.isEmpty()) { |
|
|
|
logger.setLevel(defaultLevel); |
|
} else { |
|
logger.setLevel(null); |
|
} |
|
} |
|
|
|
|
|
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()]); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void readConfiguration(InputStream ins) throws IOException, SecurityException { |
|
checkPermission(); |
|
|
|
// We don't want reset() and readConfiguration() to run |
|
|
|
configurationLock.lock(); |
|
try { |
|
if (globalHandlersState == STATE_SHUTDOWN) { |
|
// already in terminal state: don't even bother |
|
|
|
return; |
|
} |
|
|
|
|
|
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 |
|
|
|
reset(); |
|
|
|
try { |
|
|
|
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 |
|
|
|
throw new IOException(x.getMessage(), x); |
|
} |
|
|
|
|
|
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(); |
|
} |
|
} |
|
|
|
|
|
setLevelsOnExistingLoggers(); |
|
|
|
// Note that we need to reinitialize global handles when |
|
|
|
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 |
|
|
|
globalHandlersState = STATE_INITIALIZED; |
|
|
|
throw t; |
|
} |
|
} finally { |
|
configurationLock.unlock(); |
|
} |
|
|
|
// should be called out of lock to avoid dead-lock situations |
|
|
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static String getLoggerName(String property) { |
|
for (ConfigProperty p : ConfigProperty.ALL) { |
|
if (p.handleKey(property)) { |
|
return p.loggerName(property); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static Optional<ConfigProperty> find(String property) { |
|
return ConfigProperty.ALL.stream() |
|
.filter(p -> p.handleKey(property)) |
|
.findFirst(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static boolean matches(String property) { |
|
return find(property).isPresent(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static void merge(String k, Properties previous, Properties next, |
|
BiFunction<String, String, String> 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<ConfigProperty> ALL = |
|
EnumSet.allOf(ConfigProperty.class); |
|
} |
|
|
|
|
|
private static String trim(String value) { |
|
return value == null ? null : value.trim(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static final class VisitedLoggers implements Predicate<Logger> { |
|
final IdentityHashMap<Logger,Boolean> visited; |
|
private VisitedLoggers(IdentityHashMap<Logger,Boolean> 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 |
|
|
|
static final VisitedLoggers NEVER = new VisitedLoggers(null); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
static enum ModType { |
|
SAME, |
|
// same value in both. |
|
ADDED, |
|
CHANGED, |
|
REMOVED; |
|
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; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void updateConfiguration(Function<String, BiFunction<String,String,String>> 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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void updateConfiguration(InputStream ins, |
|
Function<String, BiFunction<String,String,String>> mapper) |
|
throws IOException { |
|
checkPermission(); |
|
ensureLogManagerInitialized(); |
|
drainLoggerRefQueueBounded(); |
|
|
|
final Properties previous; |
|
final Set<String> updatePropertyNames; |
|
List<LoggerContext> cxs = Collections.emptyList(); |
|
final VisitedLoggers visited = new VisitedLoggers(); |
|
final Properties next = new Properties(); |
|
|
|
try { |
|
|
|
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 |
|
|
|
throw new IOException(x.getMessage(), x); |
|
} |
|
|
|
if (globalHandlersState == STATE_SHUTDOWN) return; |
|
|
|
// exclusive lock: readConfiguration/reset/updateConfiguration can't |
|
// run concurrently. |
|
|
|
configurationLock.lock(); |
|
try { |
|
if (globalHandlersState == STATE_SHUTDOWN) return; |
|
previous = props; |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
final Stream<String> 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 |
|
|
|
final Map<String, TreeSet<String>> loggerConfigs = |
|
allKeys.collect(Collectors.groupingBy(ConfigProperty::getLoggerName, |
|
TreeMap::new, |
|
Collectors.toCollection(TreeSet::new))); |
|
|
|
if (!loggerConfigs.isEmpty()) { |
|
cxs = contexts(); |
|
} |
|
final List<Logger> loggers = cxs.isEmpty() |
|
? Collections.emptyList() : new ArrayList<>(cxs.size()); |
|
for (Map.Entry<String, TreeSet<String>> 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... |
|
|
|
final String name = e.getKey(); |
|
final Set<String> 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); |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
for (Logger l : loggers) { |
|
l.setUseParentHandlers(useParent); |
|
} |
|
} |
|
} |
|
break; |
|
case HANDLERS: |
|
List<Handler> hdls = null; |
|
if (name.isEmpty()) { |
|
|
|
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<String> names = cx.getLoggerNames() ; names.hasMoreElements();) { |
|
String name = names.nextElement(); |
|
if (name.isEmpty()) continue; |
|
Logger l = cx.findLogger(name); |
|
if (l != null && !visited.test(l)) { |
|
// should pass visited here to cut the processing when |
|
|
|
cx.processParentHandlers(l, name, visited); |
|
} |
|
} |
|
} |
|
|
|
|
|
invokeConfigurationListeners(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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. |
|
} |
|
|
|
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 |
|
|
|
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. |
|
} |
|
|
|
return defaultValue; |
|
} |
|
|
|
// Private method to load the global handlers. |
|
// We do the real work lazily, when the global handlers |
|
|
|
private void initializeGlobalHandlers() { |
|
int state = globalHandlersState; |
|
if (state == STATE_INITIALIZED || |
|
state == STATE_SHUTDOWN) { |
|
|
|
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) |
|
|
|
configurationLock.lock(); |
|
try { |
|
if (globalHandlersState != STATE_UNINITIALIZED) { |
|
return; |
|
} |
|
// set globalHandlersState to STATE_INITIALIZING first to avoid |
|
// getting an infinite recursion when loadLoggerHandlers(...) |
|
|
|
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() { |
|
@SuppressWarnings("removal") |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) |
|
sm.checkPermission(controlPermission); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Deprecated(since="17", forRemoval=true) |
|
public void checkAccess() throws SecurityException { |
|
checkPermission(); |
|
} |
|
|
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
super("", null, null, LogManager.this, true); |
|
} |
|
|
|
@Override |
|
public void log(LogRecord record) { |
|
|
|
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 |
|
|
|
private void setLevelsOnExistingLoggers() { |
|
Enumeration<?> enum_ = props.propertyNames(); |
|
while (enum_.hasMoreElements()) { |
|
String key = (String)enum_.nextElement(); |
|
if (!key.endsWith(".level")) { |
|
|
|
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); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static final String LOGGING_MXBEAN_NAME |
|
= "java.util.logging:type=Logging"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Deprecated(since="9") |
|
public static synchronized LoggingMXBean getLoggingMXBean() { |
|
return Logging.getInstance(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public LogManager addConfigurationListener(Runnable listener) { |
|
final Runnable r = Objects.requireNonNull(listener); |
|
checkPermission(); |
|
@SuppressWarnings("removal") |
|
final SecurityManager sm = System.getSecurityManager(); |
|
@SuppressWarnings("removal") |
|
final AccessControlContext acc = |
|
sm == null ? null : AccessController.getContext(); |
|
final PrivilegedAction<Void> pa = |
|
acc == null ? null : () -> { r.run() ; return null; }; |
|
@SuppressWarnings("removal") |
|
final Runnable pr = |
|
acc == null ? r : () -> AccessController.doPrivileged(pa, acc); |
|
|
|
listeners.putIfAbsent(r, pr); |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
if (t instanceof Error) throw (Error)t; |
|
if (t instanceof RuntimeException) throw (RuntimeException)t; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final class LoggingProviderAccess |
|
implements LoggingProviderImpl.LogManagerAccess, |
|
PrivilegedAction<Void> { |
|
|
|
private LoggingProviderAccess() { |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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 |
|
|
|
throw new IllegalArgumentException("manager"); |
|
} |
|
Objects.requireNonNull(name); |
|
Objects.requireNonNull(module); |
|
@SuppressWarnings("removal") |
|
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 { |
|
initStatic(); |
|
} |
|
|
|
@SuppressWarnings("removal") |
|
private static void initStatic() { |
|
AccessController.doPrivileged(LoggingProviderAccess.INSTANCE, null, |
|
controlPermission); |
|
} |
|
|
|
} |