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
+ // this handler's level.
+ String levs = getProperty(type + ".level");
+ if (levs != null) {
+ Level l = Level.findLevel(levs);
+ if (l != null) {
+ hdl.setLevel(l);
+ } else {
+ // Probably a bad level. Drop through.
+ System.err.println("Can't set level for " + type);
}
}
-
- return null;
+ // Add this Handler to the logger
+ handlers.add(hdl);
+ } catch (Exception ex) {
+ System.err.println("Can't load log handler \"" + type + "\"");
+ System.err.println("" + ex);
+ ex.printStackTrace();
}
- });
+ }
+
+ return handlers;
}
@@ -1242,21 +1259,48 @@
}
/**
- * Reinitialize the logging properties and reread the logging configuration.
+ * Reads and initializes the logging configuration.
+ *
+ * If the "java.util.logging.config.class" system property is set, then the
+ * property value is treated as a class name. The given class will be
+ * loaded, an object will be instantiated, and that object's constructor
+ * is responsible for reading in the initial configuration. (That object
+ * may use other system properties to control its configuration.) The
+ * alternate configuration class can use {@code readConfiguration(InputStream)}
+ * to define properties in the LogManager.
*
- * The same rules are used for locating the configuration properties
- * as are used at startup. So normally the logging properties will
- * be re-read from the same file that was used at startup.
- *
- * Any log level definitions in the new configuration file will be
- * applied using Logger.setLevel(), if the target Logger exists.
+ * If "java.util.logging.config.class" system property is not set,
+ * then this method will read the initial configuration from a properties
+ * file and calls the {@link #readConfiguration(InputStream)} method to initialize
+ * the configuration. The "java.util.logging.config.file" system property can be used
+ * to specify the properties file that will be read as the initial configuration;
+ * if not set, then the LogManager default configuration is used.
+ * The default configuration is typically loaded from the
+ * properties file "{@code conf/logging.properties}" in the Java installation
+ * directory.
+ *
*
* Any {@linkplain #addConfigurationListener registered configuration
* listener} will be invoked after the properties are read.
*
- * @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.
+ * @apiNote This {@code readConfiguration} method should only be used for
+ * initializing the configuration during LogManager initialization or
+ * used with the "java.util.logging.config.class" property.
+ * When this method is called after loggers have been created, and
+ * the "java.util.logging.config.class" system property is not set, all
+ * existing loggers will be {@linkplain #reset() reset}. Then any
+ * existing loggers that have a level property specified in the new
+ * configuration stream will be {@linkplain
+ * Logger#setLevel(java.util.logging.Level) set} to the specified log level.
+ *
+ * To properly update the logging configuration, use the
+ * {@link #updateConfiguration(java.util.function.Function)} or
+ * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)}
+ * methods instead.
+ *
+ * @exception SecurityException if a security manager exists and if
+ * the caller does not have LoggingPermission("control").
+ * @exception IOException if there are IO problems reading the configuration.
*/
public void readConfiguration() throws IOException, SecurityException {
checkPermission();
@@ -1284,20 +1328,24 @@
}
}
+ 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;
}
/**
@@ -1305,7 +1353,8 @@
*
* For all named loggers, the reset operation removes and closes
* all Handlers and (except for the root logger) sets the level
- * to null. The root logger's level is set to Level.INFO.
+ * to null. The root logger's level is set to Level.INFO, and
+ * its handlers will be recreated lazily when they are first used.
*
* @exception SecurityException if a security manager exists and if
* the caller does not have LoggingPermission("control").
@@ -1421,15 +1470,27 @@
}
/**
- * Reinitialize the logging properties and reread the logging configuration
- * from the given stream, which should be in java.util.Properties format.
+ * Reads and initializes the logging configuration from the given input stream,
+ * which should be in {@linkplain java.util.Properties properties file} format.
+ *
* Any {@linkplain #addConfigurationListener registered configuration
* listener} will be invoked after the properties are read.
*
- * Any log level definitions in the new configuration file will be
- * applied using Logger.setLevel(), if the target Logger exists.
+ * @apiNote This {@code readConfiguration} method should only be used for
+ * initializing the configuration during LogManager initialization or
+ * used with the "java.util.logging.config.class" property.
+ * When this method is called after loggers have been created, all
+ * existing loggers will be {@linkplain #reset() reset}. Then any
+ * existing loggers that have a level property specified in the new
+ * configuration stream will be {@linkplain
+ * Logger#setLevel(java.util.logging.Level) set} to the specified log level.
+ *
+ * To properly update the logging configuration, use the
+ * {@link #updateConfiguration(java.util.function.Function)} or
+ * {@link #updateConfiguration(java.io.InputStream, java.util.function.Function)}
+ * methods instead.
*
- * @param ins stream to read properties from
+ * @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.
@@ -1506,6 +1567,614 @@
invokeConfigurationListeners();
}
+ // This enum enumerate the configuration properties that will be
+ // updated on existing loggers when the configuration is updated
+ // with LogManager.updateConfiguration().
+ //
+ // Note that this works properly only for the global LogManager - as
+ // Handler and its subclasses get their configuration from
+ // LogManager.getLogManager().
+ //
+ static enum ConfigProperty {
+ LEVEL(".level"), HANDLERS(".handlers"), USEPARENT(".useParentHandlers");
+ final String suffix;
+ final int length;
+ private ConfigProperty(String suffix) {
+ this.suffix = Objects.requireNonNull(suffix);
+ length = suffix.length();
+ }
+
+ public boolean handleKey(String key) {
+ if (this == HANDLERS && suffix.substring(1).equals(key)) return true;
+ if (this == HANDLERS && suffix.equals(key)) return false;
+ return key.endsWith(suffix);
+ }
+ String key(String loggerName) {
+ if (this == HANDLERS && (loggerName == null || loggerName.isEmpty())) {
+ return suffix.substring(1);
+ }
+ return loggerName + suffix;
+ }
+ String loggerName(String key) {
+ assert key.equals(suffix.substring(1)) && this == HANDLERS || key.endsWith(suffix);
+ if (this == HANDLERS && suffix.substring(1).equals(key)) return "";
+ return key.substring(0, key.length() - length);
+ }
+
+ /**
+ * If the property is one that should be updated on existing loggers by
+ * updateConfiguration, returns the name of the logger for which the
+ * property is configured. Otherwise, returns null.
+ * @param property a property key in 'props'
+ * @return the name of the logger on which the property is to be set,
+ * if the property is one that should be updated on existing
+ * loggers, {@code null} otherwise.
+ */
+ static String getLoggerName(String property) {
+ for (ConfigProperty p : ConfigProperty.ALL) {
+ if (p.handleKey(property)) {
+ return p.loggerName(property);
+ }
+ }
+ return null; // Not a property that should be updated.
+ }
+
+ /**
+ * Find the ConfigProperty corresponding to the given
+ * property key (may find none).
+ * @param property a property key in 'props'
+ * @return An optional containing a ConfigProperty object,
+ * if the property is one that should be updated on existing
+ * loggers, empty otherwise.
+ */
+ static Optional find(String property) {
+ return ConfigProperty.ALL.stream()
+ .filter(p -> p.handleKey(property))
+ .findFirst();
+ }
+
+ /**
+ * Returns true if the given property is one that should be updated
+ * on existing loggers.
+ * Used to filter property name streams.
+ * @param property a property key from the configuration.
+ * @return true if this property is of interest for updateConfiguration.
+ */
+ static boolean matches(String property) {
+ return find(property).isPresent();
+ }
+
+ /**
+ * Returns true if the new property value is different from the old,
+ * and therefore needs to be updated on existing loggers.
+ * @param k a property key in the configuration
+ * @param previous the old configuration
+ * @param next the new configuration
+ * @return true if the property is changing value between the two
+ * configurations.
+ */
+ static boolean needsUpdating(String k, Properties previous, Properties next) {
+ final String p = trim(previous.getProperty(k, null));
+ final String n = trim(next.getProperty(k, null));
+ return ! Objects.equals(p,n);
+ }
+
+ /**
+ * Applies the mapping function for the given key to the next
+ * configuration.
+ * If the mapping function is null then this method does nothing.
+ * Otherwise, it calls the mapping function to compute the value
+ * that should be associated with {@code key} in the resulting
+ * configuration, and applies it to {@code next}.
+ * If the mapping function returns {@code null} the key is removed
+ * from {@code next}.
+ *
+ * @param k a property key in the configuration
+ * @param previous the old configuration
+ * @param next the new configuration (modified by this function)
+ * @param remappingFunction the mapping function.
+ */
+ static void merge(String k, Properties previous, Properties next,
+ BiFunction mappingFunction) {
+ String p = trim(previous.getProperty(k, null));
+ String n = trim(next.getProperty(k, null));
+ String mapped = trim(mappingFunction.apply(p,n));
+ if (!Objects.equals(n, mapped)) {
+ if (mapped == null) {
+ next.remove(k);
+ } else {
+ next.setProperty(k, mapped);
+ }
+ }
+ }
+
+ private static final EnumSet ALL =
+ EnumSet.allOf(ConfigProperty.class);
+ }
+
+ // trim the value if not null.
+ private static String trim(String value) {
+ return value == null ? null : value.trim();
+ }
+
+ /**
+ * An object that keep track of loggers we have already visited.
+ * Used when updating configuration, to avoid processing the same logger
+ * twice.
+ */
+ static final class VisitedLoggers implements Predicate {
+ final IdentityHashMap visited;
+ private VisitedLoggers(IdentityHashMap visited) {
+ this.visited = visited;
+ }
+ VisitedLoggers() {
+ this(new IdentityHashMap<>());
+ }
+ @Override
+ public boolean test(Logger logger) {
+ return visited != null && visited.put(logger, Boolean.TRUE) != null;
+ }
+ public void clear() {
+ if (visited != null) visited.clear();
+ }
+
+ // An object that considers that no logger has ever been visited.
+ // This is used when processParentHandlers is called from
+ // LoggerContext.addLocalLogger
+ static final VisitedLoggers NEVER = new VisitedLoggers(null);
+ }
+
+
+ /**
+ * Type of the modification for a given property. One of SAME, ADDED, CHANGED,
+ * or REMOVED.
+ */
+ static enum ModType {
+ SAME, // property had no value in the old and new conf, or had the
+ // same value in both.
+ ADDED, // property had no value in the old conf, but has one in the new.
+ CHANGED, // property has a different value in the old conf and the new conf.
+ REMOVED; // property has no value in the new conf, but had one in the old.
+ static ModType of(String previous, String next) {
+ if (previous == null && next != null) {
+ return ADDED;
+ }
+ if (next == null && previous != null) {
+ return REMOVED;
+ }
+ if (!Objects.equals(trim(previous), trim(next))) {
+ return CHANGED;
+ }
+ return SAME;
+ }
+ }
+
+ /**
+ * Updates the logging configuration.
+ *
+ * This method reads the new configuration from a
+ * properties file and calls the {@link
+ * #updateConfiguration(java.io.InputStream, java.util.function.Function)
+ * updateConfiguration(ins, mapper)} method to
+ * update the configuration.
+ *
+ * If the "java.util.logging.config.class" system property is set, this
+ * method simply ignores it.
+ *
+ * The "java.util.logging.config.file" system property can be used
+ * to specify the properties file that will be read as the new configuration;
+ * if not set, then the LogManager default configuration is used.
+ * The default configuration is typically loaded from the
+ * properties file "{@code conf/logging.properties}" in the Java installation
+ * directory.
+ *
+ * @param mapper a functional interface that takes a configuration
+ * key k and returns a function f(o,n) whose returned
+ * value v will be applied to the resulting configuration.
+ * For each configuration key k, the mapped function f will
+ * be invoked with the value associated with k in the old
+ * configuration (i.e o) and the value associated with
+ * k in the new configuration (i.e. n).
+ *
A {@code null} value for o or n indicates that no
+ * value was present for k in the corresponding configuration.
+ * A {@code null} value for v indicates that there should be no
+ * value associated with k in the resulting configuration.
+ *
+ * @throws SecurityException if a security manager exists and if
+ * the caller does not have LoggingPermission("control"), or
+ * does not have the permissions required to set up the
+ * configuration (e.g. open file specified for FileHandlers
+ * etc...)
+ *
+ * @throws NullPointerException if {@code mapper} returns a {@code null}
+ * function when invoked.
+ *
+ * @throws IOException if there are problems reading from the
+ * logging configuration file.
+ *
+ * @see #updateConfiguration(java.io.InputStream, java.util.function.Function)
+ */
+ public void updateConfiguration(Function> mapper)
+ throws IOException {
+ checkPermission();
+ ensureLogManagerInitialized();
+ drainLoggerRefQueueBounded();
+
+ String fname = getConfigurationFileName();
+ try (final InputStream in = new FileInputStream(fname)) {
+ final BufferedInputStream bin = new BufferedInputStream(in);
+ updateConfiguration(bin, mapper);
+ }
+ }
+
+ /**
+ * Updates the logging configuration.
+ *
+ * For each configuration key in the {@linkplain
+ * #getProperty(java.lang.String) existing configuration} and
+ * the given input stream configuration, the given {@code mapper} function
+ * is invoked to map from the configuration key to a function,
+ * f(o,n), that takes the old value and new value and returns
+ * the resulting value to be applied in the resulting configuration,
+ * as specified in the table below.
+ *
Let k be a configuration key in the old or new configuration,
+ * o be the old value (i.e. the value associated
+ * with k in the old configuration), n be the
+ * new value (i.e. the value associated with k in the new
+ * configuration), and f be the function returned
+ * by {@code mapper.apply(}k{@code )}: then v = f(o,n) is the
+ * resulting value (i.e. the value that will be associated with
+ * k in the resulting configuration).
+ *
+ * LogManager properties are updated with the resulting value in the
+ * resulting configuration.
+ *
A {@code null} value may be passed to function
+ * f to indicate that the corresponding configuration has no
+ * configuration key k.
+ * A {@code null} value may be returned by f to indicate that
+ * there should be no value associated with k in the resulting
+ * configuration.
+ *
+ * If {@code mapper} is {@code null}, then v will be set to
+ * n.
+ *
+ * The registered {@linkplain #addConfigurationListener configuration
+ * listener} will be invoked after the configuration is successfully updated.
+ *
+ *
+ *
+ * Property |
+ * Resulting Behavior |
+ *
+ *
+ * {@code .level} |
+ *
+ *
+ * - If the resulting configuration defines a level for a logger and
+ * if the resulting level is different than the level specified in the
+ * the old configuration, or not specified in
+ * the old configuration, then if the logger exists or if children for
+ * that logger exist, the level for that logger will be updated,
+ * and the change propagated to any existing logger children.
+ * This may cause the logger to be created, if necessary.
+ *
+ * - If the old configuration defined a level for a logger, and the
+ * resulting configuration doesn't, then this change will not be
+ * propagated to existing loggers, if any.
+ * To completely replace a configuration - the caller should therefore
+ * call {@link #reset() reset} to empty the current configuration,
+ * before calling {@code updateConfiguration}.
+ *
+ *
+ * |
+ *
+ * {@code .useParentHandlers} |
+ *
+ *
+ * - If either the resulting or the old value for the useParentHandlers
+ * property is not null, then if the logger exists or if children for
+ * that logger exist, that logger will be updated to the resulting
+ * value.
+ * The value of the useParentHandlers property is the value specified
+ * in the configuration; if not specified, the default is true.
+ *
+ *
+ * |
+ *
+ *
+ * {@code .handlers} |
+ *
+ *
+ * - If the resulting configuration defines a list of handlers for a
+ * logger, and if the resulting list is different than the list
+ * specified in the old configuration for that logger (that could be
+ * empty), then if the logger exists or its children exist, the
+ * handlers associated with that logger are closed and removed and
+ * the new handlers will be created per the resulting configuration
+ * and added to that logger, creating that logger if necessary.
+ *
+ * - If the old configuration defined some handlers for a logger, and
+ * the resulting configuration doesn't, if that logger exists,
+ * its handlers will be removed and closed.
+ *
+ * - Changing the list of handlers on an existing logger will cause all
+ * its previous handlers to be removed and closed, regardless of whether
+ * they had been created from the configuration or programmatically.
+ * The old handlers will be replaced by new handlers, if any.
+ *
+ *
+ * |
+ *
+ *
+ * {@code .*} |
+ *
+ *
+ * - Properties configured/changed on handler classes will only affect
+ * newly created handlers. If a node is configured with the same list
+ * of handlers in the old and the resulting configuration, then these
+ * handlers will remain unchanged.
+ *
+ *
+ * |
+ *
+ *
+ * {@code config} and any other property |
+ *
+ *
+ * - The resulting value for these property will be stored in the
+ * LogManager properties, but {@code updateConfiguration} will not parse
+ * or process their values.
+ *
+ *
+ * |
+ *
+ *
+ *
+ * Example mapper functions:
+ *
+ *
+ * - Replace all logging properties with the new configuration:
+ *
+ * {@code (k) -> ((o, n) -> n)}
+ *
+ * This is equivalent to passing a null {@code mapper} parameter
+ * - Merge the new configuration and old configuration and use the
+ * new value if k exists in the new configuration.
+ *
+ * {@code (k) -> ((o, n) -> n == null ? o : n)}:
+ *
+ * as if merging two collections as follows:
+ *
+ * {@code result.putAll(oldc); result.putAll(newc)}.
+ * - Merge the new configuration and old configuration and use the old
+ * value if k exists in the old configuration:
+ *
+ * {@code (k) -> ((o, n) -> o == null ? n : o)}
+ *
+ * as if merging two collections as follows:
+ *
+ * {@code result.putAll(newc); result.putAll(oldc)}.
+ * - Replace all properties with the new configuration except the handler
+ * property to configure Logger's handler that is not root logger:
+ *
+ * {@code (k) -> k.endsWith(".handlers")}
+ * {@code ? ((o, n) -> (o == null ? n : o))}
+ * {@code : ((o, n) -> n)}
+ *
+ *
+ *
+ * To completely reinitialize a configuration, an application can first call
+ * {@link #reset() reset} to fully remove the old configuration, followed by
+ * {@code updateConfiguration} to initialize the new configuration.
+ *
+ * @param ins a stream to read properties from
+ * @param mapper a functional interface that takes a configuration
+ * key k and returns a function f(o,n) whose returned
+ * value v will be applied to the resulting configuration.
+ * For each configuration key k, the mapped function f will
+ * be invoked with the value associated with k in the old
+ * configuration (i.e o) and the value associated with
+ * k in the new configuration (i.e. n).
+ *
A {@code null} value for o or n indicates that no
+ * value was present for k in the corresponding configuration.
+ * A {@code null} value for v indicates that there should be no
+ * value associated with k in the resulting configuration.
+ *
+ * @throws SecurityException if a security manager exists and if
+ * the caller does not have LoggingPermission("control"), or
+ * does not have the permissions required to set up the
+ * configuration (e.g. open files specified for FileHandlers)
+ *
+ * @throws NullPointerException if {@code ins} is null or if
+ * {@code mapper} returns a null function when invoked.
+ *
+ * @throws IOException if there are problems reading from the
+ * logging configuration file.
+ */
+ public void updateConfiguration(InputStream ins,
+ Function> mapper)
+ throws IOException {
+ checkPermission();
+ ensureLogManagerInitialized();
+ drainLoggerRefQueueBounded();
+
+ final Properties previous;
+ final Set updatePropertyNames;
+ List cxs = Collections.emptyList();
+ final VisitedLoggers visited = new VisitedLoggers();
+ final Properties next = new Properties();
+ next.load(ins);
+
+ if (globalHandlersState == STATE_SHUTDOWN) return;
+
+ // exclusive lock: readConfiguration/reset/updateConfiguration can't
+ // run concurrently.
+ // configurationLock.writeLock().lock();
+ configurationLock.lock();
+ try {
+ if (globalHandlersState == STATE_SHUTDOWN) return;
+ previous = props;
+
+ // Builds a TreeSet of all (old and new) property names.
+ updatePropertyNames =
+ Stream.concat(previous.stringPropertyNames().stream(),
+ next.stringPropertyNames().stream())
+ .collect(Collectors.toCollection(TreeSet::new));
+
+ if (mapper != null) {
+ // mapper will potentially modify the content of
+ // 'next', so we need to call it before affecting props=next.
+ // give a chance to the mapper to control all
+ // properties - not just those we will reset.
+ updatePropertyNames.stream()
+ .forEachOrdered(k -> ConfigProperty
+ .merge(k, previous, next,
+ Objects.requireNonNull(mapper.apply(k))));
+ }
+
+ props = next;
+
+ // allKeys will contain all keys:
+ // - which correspond to a configuration property we are interested in
+ // (first filter)
+ // - whose value needs to be updated (because it's new, removed, or
+ // different) in the resulting configuration (second filter)
+ final Stream allKeys = updatePropertyNames.stream()
+ .filter(ConfigProperty::matches)
+ .filter(k -> ConfigProperty.needsUpdating(k, previous, next));
+
+ // Group configuration properties by logger name
+ // We use a TreeMap so that parent loggers will be visited before
+ // child loggers.
+ final Map> loggerConfigs =
+ allKeys.collect(Collectors.groupingBy(ConfigProperty::getLoggerName,
+ TreeMap::new,
+ Collectors.toCollection(TreeSet::new)));
+
+ if (!loggerConfigs.isEmpty()) {
+ cxs = contexts();
+ }
+ final List loggers = cxs.isEmpty()
+ ? Collections.emptyList() : new ArrayList<>(cxs.size());
+ for (Map.Entry> e : loggerConfigs.entrySet()) {
+ // This can be a logger name, or something else...
+ // The only thing we know is that we found a property
+ // we are interested in.
+ // For instance, if we found x.y.z.level, then x.y.z could be
+ // a logger, but it could also be a handler class...
+ // Anyway...
+ final String name = e.getKey();
+ final Set properties = e.getValue();
+ loggers.clear();
+ for (LoggerContext cx : cxs) {
+ Logger l = cx.findLogger(name);
+ if (l == null) continue;
+ if (!visited.test(l)) {
+ loggers.add(l);
+ }
+ }
+ if (loggers.isEmpty()) continue;
+ for (String pk : properties) {
+ ConfigProperty cp = ConfigProperty.find(pk).get();
+ String p = previous.getProperty(pk, null);
+ String n = next.getProperty(pk, null);
+
+ // Determines the type of modification.
+ ModType mod = ModType.of(p, n);
+
+ // mod == SAME means that the two values are equals, there
+ // is nothing to do. Usually, this should not happen as such
+ // properties should have been filtered above.
+ // It could happen however if the properties had
+ // trailing/leading whitespaces.
+ if (mod == ModType.SAME) continue;
+
+ switch (cp) {
+ case LEVEL:
+ if (mod == ModType.REMOVED) continue;
+ Level level = Level.findLevel(trim(n));
+ if (level == null) {
+ if (name.isEmpty()) {
+ rootLogger.setLevel(level);
+ }
+ for (Logger l : loggers) {
+ if (!name.isEmpty() || l != rootLogger) {
+ l.setLevel(level);
+ }
+ }
+ }
+ break;
+ case USEPARENT:
+ if (!name.isEmpty()) {
+ boolean useParent = getBooleanProperty(pk, true);
+ if (n != null || p != null) {
+ // reset the flag only if the previous value
+ // or the new value are not null.
+ for (Logger l : loggers) {
+ l.setUseParentHandlers(useParent);
+ }
+ }
+ }
+ break;
+ case HANDLERS:
+ List hdls = null;
+ if (name.isEmpty()) {
+ // special handling for the root logger.
+ globalHandlersState = STATE_READING_CONFIG;
+ try {
+ closeHandlers(rootLogger);
+ globalHandlersState = STATE_UNINITIALIZED;
+ } catch (Throwable t) {
+ globalHandlersState = STATE_INITIALIZED;
+ throw t;
+ }
+ }
+ for (Logger l : loggers) {
+ if (l == rootLogger) continue;
+ closeHandlers(l);
+ if (mod == ModType.REMOVED) {
+ closeOnResetLoggers.removeIf(c -> c.logger == l);
+ continue;
+ }
+ if (hdls == null) {
+ hdls = name.isEmpty()
+ ? Arrays.asList(rootLogger.getHandlers())
+ : createLoggerHandlers(name, pk);
+ }
+ setLoggerHandlers(l, name, pk, hdls);
+ }
+ break;
+ default: break;
+ }
+ }
+ }
+ } finally {
+ configurationLock.unlock();
+ visited.clear();
+ }
+
+ // Now ensure that if an existing logger has acquired a new parent
+ // in the configuration, this new parent will be created - if needed,
+ // and added to the context of the existing child.
+ //
+ drainLoggerRefQueueBounded();
+ for (LoggerContext cx : cxs) {
+ for (Enumeration names = cx.getLoggerNames() ; names.hasMoreElements();) {
+ String name = names.nextElement();
+ if (name.isEmpty()) continue; // don't need to process parents on root.
+ Logger l = cx.findLogger(name);
+ if (l != null && !visited.test(l)) {
+ // should pass visited here to cut the processing when
+ // reaching a logger already visited.
+ cx.processParentHandlers(l, name, visited);
+ }
+ }
+ }
+
+ // We changed the configuration: invoke configuration listeners
+ invokeConfigurationListeners();
+ }
+
/**
* Get the value of a logging property.
* The method returns null if the property is not found.