< prev index next >
src/java.logging/share/classes/java/util/logging/LogManager.java
Print this page
@@ -30,12 +30,19 @@
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.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import sun.misc.JavaAWTAccess;
import sun.misc.ManagedLocalsThread;
import sun.misc.SharedSecrets;
/**
@@ -786,11 +793,11 @@
}
// 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);
+ processParentHandlers(logger, name, VisitedLoggers.of(null));
// Find the new node and its parent.
LogNode node = getNode(name);
node.loggerRef = ref;
Logger parent = null;
@@ -834,11 +841,12 @@
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) {
+ private void processParentHandlers(final Logger logger, final String name,
+ BiPredicate<Logger,String> visited) {
final LogManager owner = getOwner();
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
if (logger != owner.rootLogger) {
@@ -860,11 +868,13 @@
String pname = name.substring(0, ix2);
if (owner.getProperty(pname + ".level") != null ||
owner.getProperty(pname + ".handlers") != null) {
// This pname has a level/handlers definition.
// Make sure it exists.
- demandLogger(pname, null, null);
+ if (visited.test(demandLogger(pname, null, null), pname)) {
+ break;
+ }
}
ix = ix2+1;
}
}
@@ -940,18 +950,40 @@
// be made based on the logging configuration, which can
// only be modified by trusted code.
private void loadLoggerHandlers(final Logger logger, final String name,
final String handlersPropertyName)
{
- AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
- public Object run() {
- String names[] = parseClassNames(handlersPropertyName);
- final boolean ensureCloseOnReset = names.length > 0
- && getBooleanProperty(handlersPropertyName + ".ensureCloseOnReset",true);
+ 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) {
+ // add this logger to the closeOnResetLoggers list.
+ 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 {
Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(type);
Handler hdl = (Handler) clz.newInstance();
// Check if there is a property defining the
@@ -965,25 +997,19 @@
// Probably a bad level. Drop through.
System.err.println("Can't set level for " + type);
}
}
// Add this Handler to the logger
- logger.addHandler(hdl);
- if (++count == 1 && ensureCloseOnReset) {
- // add this logger to the closeOnResetLoggers list.
- closeOnResetLoggers.addIfAbsent(CloseOnReset.create(logger));
- }
+ handlers.add(hdl);
} catch (Exception ex) {
System.err.println("Can't load log handler \"" + type + "\"");
System.err.println("" + ex);
ex.printStackTrace();
}
}
- return null;
- }
- });
+ return handlers;
}
// loggerRefQueue holds LoggerWeakRef objects for Logger objects
// that have been GC'ed.
@@ -1252,10 +1278,21 @@
* applied using Logger.setLevel(), if the target Logger exists.
* <p>
* Any {@linkplain #addConfigurationListener registered configuration
* listener} will be invoked after the properties are read.
*
+ * @apiNote {@code readConfiguration} is principally useful for
+ * establishing the LogManager primordial configuration.
+ * <p>
+ * Calling this method directly from the application code after the
+ * LogManager has been initialized is discouraged.
+ *
+ * Applications that wish to update the LogManager
+ * configuration after the LogManager has been initialized should
+ * call {@link #updateConfiguration(java.util.function.Function)}
+ * instead.
+ *
* @exception SecurityException if a security manager exists and if
* the caller does not have LoggingPermission("control").
* @exception IOException if there are IO problems reading the configuration.
*/
public void readConfiguration() throws IOException, SecurityException {
@@ -1282,24 +1319,28 @@
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 ??");
}
- File f = new File(fname, "conf");
- f = new File(f, "logging.properties");
- fname = f.getCanonicalPath();
- }
- try (final InputStream in = new FileInputStream(fname)) {
- final BufferedInputStream bin = new BufferedInputStream(in);
- readConfiguration(bin);
+ fname = Paths.get(fname, "conf", "logging.properties")
+ .toAbsolutePath().normalize().toString();
}
+ return fname;
}
/**
* Reset the logging configuration.
* <p>
@@ -1427,10 +1468,21 @@
* listener} will be invoked after the properties are read.
* <p>
* Any log level definitions in the new configuration file will be
* applied using Logger.setLevel(), if the target Logger exists.
*
+ * @apiNote {@code readConfiguration} is principally useful for applications
+ * which use the {@code "java.util.logging.config.class"} property
+ * to establish their own custom configuration.
+ * <p>
+ * Calling this method directly from the application code after the
+ * LogManager has been initialized is discouraged.
+ * Applications that wish to update the LogManager configuration after
+ * the LogManager has been initialized should call
+ * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)}
+ * instead.
+ *
* @param ins stream to read properties from
* @exception SecurityException if a security manager exists and if
* the caller does not have LoggingPermission("control").
* @exception IOException if there are problems reading from the stream.
*/
@@ -1504,10 +1556,566 @@
// 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
+ // re-established 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 ConfigurationProperties implements Predicate<String> {
+ LEVEL(".level"), HANDLERS(".handlers"), USEPARENT(".useParentHandlers");
+ final String suffix;
+ final int length;
+ private ConfigurationProperties(String suffix) {
+ this.suffix = Objects.requireNonNull(suffix);
+ length = suffix.length();
+ }
+ @Override
+ public boolean test(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 reestablished 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 we should reestablish.
+ * {@code null} otherwise.
+ */
+ static String getLoggerName(String property) {
+ for (ConfigurationProperties p : ConfigurationProperties.ALL) {
+ if (p.test(property)) {
+ return p.loggerName(property);
+ }
+ }
+ return null; // Not a property that should be reestablished.
+ }
+
+ /**
+ * If the property is one that should be reestablished by
+ * updateConfiguration, returns the corresponding
+ * ConfigurationProperties object. Otherwise, returns null.
+ * @param property a property key in 'props'
+ * @return the corresponding ConfigurationProperties object,
+ * if the property is one that we should reestablish.
+ * {@code null} otherwise.
+ */
+ static ConfigurationProperties of(String property) {
+ for (ConfigurationProperties p : ConfigurationProperties.ALL) {
+ if (p.test(property)) {
+ return p;
+ }
+ }
+ return null; // Not a property that should be reestablished.
+ }
+
+ /**
+ * Returns true if the given property is one that should be reestablished.
+ * 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 isOf(String property) {
+ return of(property) != null;
+ }
+
+ /**
+ * Returns true if the new property value is different from the old.
+ * @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 isChanging(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 remapping function for the given key to the next
+ * configuration.
+ * If the remapping function is null then this method does nothing.
+ * Otherwise, it calls the remapping function to compute the value
+ * that should be associated with {@code key} in the resulting
+ * configuration, and applies it to {@code next}.
+ * If the remapping 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 remappingFunction the remapping function.
+ */
+ static void merge(String k, Properties previous, Properties next,
+ BiFunction<String, String, String> remappingFunction) {
+ if (remappingFunction != null) {
+ String p = trim(previous.getProperty(k, null));
+ String n = trim(next.getProperty(k, null));
+ String mapped = trim(remappingFunction.apply(p,n));
+ if (!Objects.equals(n, mapped)) {
+ if (mapped == null) {
+ next.remove(k);
+ } else {
+ next.setProperty(k, mapped);
+ }
+ }
+ }
+ }
+
+ private static final EnumSet<ConfigurationProperties> ALL =
+ EnumSet.allOf(ConfigurationProperties.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 BiPredicate<Logger, String> {
+ final Map<Logger,String> visited;
+ private VisitedLoggers(Map<Logger,String> visited) {
+ this.visited = visited;
+ }
+ @Override
+ public boolean test(Logger logger, String name) {
+ return visited != null && visited.put(logger, name) != null;
+ }
+ public void clear() {
+ if (visited != null) visited.clear();
+ }
+
+ public static VisitedLoggers of(IdentityHashMap<Logger, String> visited) {
+ return visited == null ? NEVER : new VisitedLoggers(visited);
+ }
+
+ // 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 ADDED, CHANGED,
+ * or REMOVED.
+ */
+ static enum ModType {
+ 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 null;
+ }
+ }
+
+ /**
+ * Updates an existing configuration.
+ * <p>
+ * @implSpec
+ * This is equivalent to calling:
+ * <pre>
+ * try (final InputStream in = new FileInputStream(<logging.properties>)) {
+ * final BufferedInputStream bin = new BufferedInputStream(in);
+ * updateConfiguration(bin, mapper);
+ * }
+ * </pre>
+ * where {@code <logging.properties>} is the logging configuration file path.
+ * <p>
+ * Note that this method not take into account the value of the
+ * {@code "java.util.logging.config.class"} property.
+ * The {@code "java.util.logging.config.file"} system property is read
+ * to find the logging properties file, whether the
+ * {@code "java.util.logging.config.class"} property is set or not.
+ * If the {@code "java.util.logging.config.file"} system property is not
+ * defined then the default configuration is used.
+ * The default configuration is typically loaded from the properties file
+ * "{@code conf/logging.properties}" in the Java installation directory.
+ *
+ * @param mapper Used to control how the old configuration and
+ * candidate configuration will be mapped into the new configuration.
+ * See {@link
+ * #updateConfiguration(java.io.InputStream, java.util.function.Function)}
+ * for more details.
+ *
+ * @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 IOException if there are problems reading from the
+ * logging configuration file.
+ */
+ public void updateConfiguration(Function<String, BiFunction<String,String,String>> mapper)
+ throws IOException {
+ checkPermission();
+ String fname = getConfigurationFileName();
+ try (final InputStream in = new FileInputStream(fname)) {
+ final BufferedInputStream bin = new BufferedInputStream(in);
+ updateConfiguration(bin, mapper);
+ }
+ }
+
+ /**
+ * Update the logging configuration. The given {@code mapper} function
+ * is invoked for each configuration key in order to produce a resulting
+ * logging configuration to to be applied. The result of applying that new
+ * configuration is detailed in the table below.
+ * The registered {@linkplain #addConfigurationListener configuration
+ * listener} will be invoked after the configuration is successfully updated.
+ * <br>
+ * <table>
+ * <caption>Updating configuration properties</caption>
+ * <tr>
+ * <th>Property</th>
+ * <th>Resulting Behavior</th>
+ * </tr>
+ * <tr>
+ * <td valign="top">{@code <logger>.level}</td>
+ * <td>
+ * <ul>
+ * <li>If the new configuration defines a level for a logger and
+ * if the new level is different than the level specified in the
+ * the old configuration, or not specified in
+ * the old configuration, then the level for that logger will be
+ * updated, and the change propagated to the existing logger children,
+ * if necessary.
+ * </li>
+ * <li>If the old configuration defined a level for a logger, and the new
+ * configuration doesn't, then the logger level remains unchanged.
+ * To completely replace a configuration - the caller should therefore
+ * call {@link #reset() reset} to empty the current configuration, before
+ * calling {@code updateConfiguration}.
+ * </li>
+ * </ul>
+ * </td>
+ * <tr>
+ * <td valign="top">{@code <logger>.useParentHandlers}</td>
+ * <td>
+ * <ul>
+ * <li>If either the new 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 new value.
+ * The value of the useParentHandlers property is the value specified
+ * in the configuration; if not specified, the default is true.
+ * </li>
+ * </ul>
+ * </td>
+ * </tr>
+ * <tr>
+ * <td valign="top">{@code <logger>.handlers}</td>
+ * <td>
+ * <ul>
+ * <li>If the new configuration defines a list of handlers for a logger,
+ * and if the new list is different than the list specified in the old
+ * configuration for that logger (that could be empty), if the logger
+ * exists or whose children exist, the handlers associated with that logger
+ * are closed and removed and the new handlers will be created per the new
+ * configuration and added to that logger.
+ * </li>
+ * <li>If the old configuration defined some handlers for a logger, and
+ * the new configuration doesn't, if that logger exists,
+ * its handlers will be removed and closed.
+ * </li>
+ * <li>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.
+ * </li>
+ * </ul>
+ * </td>
+ * </tr>
+ * <tr>
+ * <td valign="top">{@code <handler-name>.*}</td>
+ * <td>
+ * <ul>
+ * <li>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 new configuration, then these handlers
+ * will remain unchanged.
+ * </li>
+ * </ul>
+ * </td>
+ * </tr>
+ * </table>
+ * <p>
+ * To completely reestablish a configuration, an application can first call
+ * {@link #reset() reset} to fully remove the old configuration, followed by
+ * {@code updateConfiguration} to establish the new configuration.
+ * <p>
+ * Note that this method has no special handling for the "config" property.
+ * The new value provided by the mapper function will be stored in the
+ * LogManager properties, but {@code updateConfiguration} will not parse its
+ * value nor instantiate the classes it points to.
+ *
+ * @param ins stream to read properties from
+ * @param mapper a functional interface that takes a configuration
+ * key and returns a remapping function for values associated
+ * with that key. The remapping function takes the old value
+ * and the candidate new value as parameters and produces the
+ * actual value to be applied. If the {@code mapper} is null,
+ * or if the remapping function it returns is null, then the
+ * candidate value will be the new value.
+ * A {@code null} value passed as parameter to the remapping
+ * function indicates that no value was present in the
+ * corresponding configuration.
+ * <p>
+ * Examples of {@code mapper} are:
+ * <ul><li>{@code (k) -> ((o, n) -> n)}: always take the new value.
+ * equivalent to passing null for {@code mapper}</li>
+ * <li>{@code (k) -> ((o, n) -> n == null ? o : n)}: keep the
+ * old value if the candidate configuration has no value
+ * for that key, otherwise take the new value.
+ * This is equivalent to adding the new
+ * configuration to the old, letting new values override
+ * old values, as what would be obtained with
+ * {@code result.putAll(old); result.putAll(candidate)}.
+ * </li>
+ * <li>{@code (k) -> ((o, n) -> o == null ? n : o)}: only
+ * take the new value if the old configuration didn't
+ * have a value for that key.
+ * This is equivalent to appending the new
+ * configuration to the old, without letting new values
+ * override old values, as what would be obtained with
+ * {@code result.putAll(candidate); result.putAll(old)}.
+ * </li>
+ * <li>
+ * <pre>{@code (k) -> k.endsWith(".handlers")}
+ * {@code ? ((o, n) -> (o == null ? n : o))}
+ * {@code : ((o, n) -> n)}</pre> do not let the new configuration
+ * override existing per logger handlers. Only root handlers
+ * can be overridden.</li>
+ * </ul>
+ * @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 IOException if there are problems reading from the
+ * logging configuration file.
+ */
+ public void updateConfiguration(InputStream ins,
+ Function<String, BiFunction<String,String,String>> mapper)
+ throws IOException {
+ checkPermission();
+ ensureLogManagerInitialized();
+ drainLoggerRefQueueBounded();
+
+ final Properties previous;
+ final Set<String> updatePropertyNames;
+ Properties next = new Properties();
+ next.load(ins);
+ List<LoggerContext> cxs = Collections.emptyList();
+ final VisitedLoggers visited = VisitedLoggers.of(new IdentityHashMap<>());
+
+ 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 -> ConfigurationProperties
+ .merge(k, previous, next, mapper.apply(k)));
+ }
+
+ props = next;
+
+ // allKeys will contain all keys:
+ // - which correspond to a configuration property we are interested in
+ // (first filter)
+ // - whose value is changing (new, removed, different) in the configuration
+ // (second filter)
+ final Stream<String> allKeys = updatePropertyNames.stream()
+ .filter(ConfigurationProperties::isOf)
+ .filter(k -> ConfigurationProperties.isChanging(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<String, TreeSet<String>> loggerConfigs =
+ allKeys.collect(
+ Collectors.groupingBy(
+ ConfigurationProperties::getLoggerName,
+ TreeMap::new,
+ Collectors.toCollection(TreeSet::new)));
+
+ if (!loggerConfigs.isEmpty()) {
+ cxs = contexts();
+ }
+ final List<Logger> tmp = 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...
+ // Anyway...
+ final String name = e.getKey();
+ final Set<String> properties = e.getValue();
+ tmp.clear();
+ for (LoggerContext cx : cxs) {
+ Logger l = cx.findLogger(name);
+ if (l == null) continue;
+ if (!visited.test(l, name)) {
+ tmp.add(l);
+ }
+ }
+ if (tmp.isEmpty()) continue;
+ for (String pk : properties) {
+ ConfigurationProperties cp = ConfigurationProperties.of(pk);
+ String p = previous.getProperty(pk, null);
+ String n = next.getProperty(pk, null);
+
+ // Determines the type of modification.
+ ModType mod = ModType.of(p, n);
+
+ // mod == null 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 == null) 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 : tmp) {
+ 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 : tmp) {
+ l.setUseParentHandlers(useParent);
+ }
+ }
+ }
+ break;
+ case HANDLERS:
+ List<Handler> 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 : tmp) {
+ 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; // don't need to process parents on root.
+ Logger l = cx.findLogger(name);
+ if (l != null && !visited.test(l, name)) {
+ // 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
< prev index next >