8159245: Loggers created by system classes are not initialized correctly when configured programmatically from application code.
Summary: Loggers of the same name now share the same configuration.
Reviewed-by: mchung, mli
--- a/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java Tue Jul 12 09:41:49 2016 +0800
+++ b/jdk/src/java.logging/share/classes/java/util/logging/LogManager.java Tue Jul 12 11:29:01 2016 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -387,11 +387,15 @@
assert rootLogger == null;
assert initializedCalled && !initializationDone;
+ // create root logger before reading primordial
+ // configuration - to ensure that it will be added
+ // before the global logger, and not after.
+ owner.rootLogger = owner.new RootLogger();
+
// Read configuration.
owner.readPrimordialConfiguration();
// Create and retain Logger for the root of the namespace.
- owner.rootLogger = owner.new RootLogger();
owner.addLogger(owner.rootLogger);
if (!owner.rootLogger.isLevelInitialized()) {
owner.rootLogger.setLevel(defaultLevel);
@@ -516,7 +520,7 @@
if (result == null) {
// only allocate the new logger once
Logger newLogger = new Logger(name, resourceBundleName,
- module == null ? null : module, this, false);
+ module, this, false);
do {
if (addLogger(newLogger)) {
// We successfully added the new Logger that we
@@ -569,15 +573,13 @@
} while (logger == null);
// LogManager will set the sysLogger's handlers via LogManager.addLogger method.
- if (logger != sysLogger && sysLogger.accessCheckedHandlers().length == 0) {
- // if logger already exists but handlers not set
+ if (logger != sysLogger) {
+ // if logger already exists we merge the two logger configurations.
final Logger l = logger;
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
- for (Handler hdl : l.accessCheckedHandlers()) {
- sysLogger.addHandler(hdl);
- }
+ l.mergeWithSystemLogger(sysLogger);
return null;
}
});
--- a/jdk/src/java.logging/share/classes/java/util/logging/Logger.java Tue Jul 12 09:41:49 2016 +0800
+++ b/jdk/src/java.logging/share/classes/java/util/logging/Logger.java Tue Jul 12 11:29:01 2016 +0100
@@ -259,13 +259,185 @@
private static final RuntimePermission GET_CLASS_LOADER_PERMISSION =
new RuntimePermission("getClassLoader");
+ // A value class that holds the logger configuration data.
+ // This configuration can be shared between an application logger
+ // and a system logger of the same name.
+ private static final class ConfigurationData {
+
+ // The delegate field is used to avoid races while
+ // merging configuration. This will ensure that any pending
+ // configuration action on an application logger will either
+ // be finished before the merge happens, or will be forwarded
+ // to the system logger configuration after the merge is completed.
+ // By default delegate=this.
+ private volatile ConfigurationData delegate;
+
+ volatile boolean useParentHandlers;
+ volatile Filter filter;
+ volatile Level levelObject;
+ volatile int levelValue; // current effective level value
+ final CopyOnWriteArrayList<Handler> handlers =
+ new CopyOnWriteArrayList<>();
+
+ ConfigurationData() {
+ delegate = this;
+ useParentHandlers = true;
+ levelValue = Level.INFO.intValue();
+ }
+
+ void setUseParentHandlers(boolean flag) {
+ useParentHandlers = flag;
+ if (delegate != this) {
+ // merge in progress - propagate value to system peer.
+ final ConfigurationData system = delegate;
+ synchronized (system) {
+ system.useParentHandlers = useParentHandlers;
+ }
+ }
+ }
+
+ void setFilter(Filter f) {
+ filter = f;
+ if (delegate != this) {
+ // merge in progress - propagate value to system peer.
+ final ConfigurationData system = delegate;
+ synchronized (system) {
+ system.filter = filter;
+ }
+ }
+ }
+
+ void setLevelObject(Level l) {
+ levelObject = l;
+ if (delegate != this) {
+ // merge in progress - propagate value to system peer.
+ final ConfigurationData system = delegate;
+ synchronized (system) {
+ system.levelObject = levelObject;
+ }
+ }
+ }
+
+ void setLevelValue(int v) {
+ levelValue = v;
+ if (delegate != this) {
+ // merge in progress - propagate value to system peer.
+ final ConfigurationData system = delegate;
+ synchronized (system) {
+ system.levelValue = levelValue;
+ }
+ }
+ }
+
+ void addHandler(Handler h) {
+ if (handlers.add(h)) {
+ if (delegate != this) {
+ // merge in progress - propagate value to system peer.
+ final ConfigurationData system = delegate;
+ synchronized (system) {
+ system.handlers.addIfAbsent(h);
+ }
+ }
+ }
+ }
+
+ void removeHandler(Handler h) {
+ if (handlers.remove(h)) {
+ if (delegate != this) {
+ // merge in progress - propagate value to system peer.
+ final ConfigurationData system = delegate;
+ synchronized (system) {
+ system.handlers.remove(h);
+ }
+ }
+ }
+ }
+
+ ConfigurationData merge(Logger systemPeer) {
+ if (!systemPeer.isSystemLogger) {
+ // should never come here
+ throw new InternalError("not a system logger");
+ }
+
+ ConfigurationData system = systemPeer.config;
+
+ if (system == this) {
+ // nothing to do
+ return system;
+ }
+
+ synchronized (system) {
+ // synchronize before checking on delegate to counter
+ // race conditions where two threads might attempt to
+ // merge concurrently
+ if (delegate == system) {
+ // merge already performed;
+ return system;
+ }
+
+ // publish system as the temporary delegate configuration.
+ // This should take care of potential race conditions where
+ // an other thread might attempt to call e.g. setlevel on
+ // the application logger while merge is in progress.
+ // (see implementation of ConfigurationData::setLevel)
+ delegate = system;
+
+ // merge this config object data into the system config
+ system.useParentHandlers = useParentHandlers;
+ system.filter = filter;
+ system.levelObject = levelObject;
+ system.levelValue = levelValue;
+
+ // Prevent race condition in case two threads attempt to merge
+ // configuration and add handlers at the same time. We don't want
+ // to add the same handlers twice.
+ //
+ // Handlers are created and loaded by LogManager.addLogger. If we
+ // reach here, then it means that the application logger has
+ // been created first and added with LogManager.addLogger, and the
+ // system logger was created after - and no handler has been added
+ // to it by LogManager.addLogger. Therefore, system.handlers
+ // should be empty.
+ //
+ // A non empty cfg.handlers list indicates a race condition
+ // where two threads might attempt to merge the configuration
+ // or add handlers concurrently. Though of no consequence for
+ // the other data (level etc...) this would be an issue if we
+ // added the same handlers twice.
+ //
+ for (Handler h : handlers) {
+ if (!system.handlers.contains(h)) {
+ systemPeer.addHandler(h);
+ }
+ }
+ system.handlers.retainAll(handlers);
+ system.handlers.addAllAbsent(handlers);
+ }
+
+ // sanity: update effective level after merging
+ synchronized(treeLock) {
+ systemPeer.updateEffectiveLevel();
+ }
+
+ return system;
+ }
+
+ }
+
+ // The logger configuration data. Ideally, this should be final
+ // for system loggers, and replace-once for application loggers.
+ // When an application requests a logger by name, we do not know a-priori
+ // whether that corresponds to a system logger name or not.
+ // So if no system logger by that name already exists, we simply return an
+ // application logger.
+ // If a system class later requests a system logger of the same name, then
+ // the application logger and system logger configurations will be merged
+ // in a single instance of ConfigurationData that both loggers will share.
+ private volatile ConfigurationData config;
+
private volatile LogManager manager;
private String name;
- private final CopyOnWriteArrayList<Handler> handlers =
- new CopyOnWriteArrayList<>();
private volatile LoggerBundle loggerBundle = NO_RESOURCE_BUNDLE;
- private volatile boolean useParentHandlers = true;
- private volatile Filter filter;
private boolean anonymous;
// Cache to speed up behavior of findResourceBundle:
@@ -280,8 +452,6 @@
// references from children to parents.
private volatile Logger parent; // our nearest parent.
private ArrayList<LogManager.LoggerWeakRef> kids; // WeakReferences to loggers that have us as parent
- private volatile Level levelObject;
- private volatile int levelValue; // current effective level value
private WeakReference<Module> callerModuleRef;
private final boolean isSystemLogger;
@@ -384,9 +554,29 @@
LogManager manager, boolean isSystemLogger) {
this.manager = manager;
this.isSystemLogger = isSystemLogger;
- setupResourceInfo(resourceBundleName, caller);
+ this.config = new ConfigurationData();
this.name = name;
- levelValue = Level.INFO.intValue();
+ setupResourceInfo(resourceBundleName, caller);
+ }
+
+ // Called by LogManager when a system logger is created
+ // after a user logger of the same name.
+ // Ensure that both loggers will share the same
+ // configuration.
+ final void mergeWithSystemLogger(Logger system) {
+ // sanity checks
+ if (!system.isSystemLogger
+ || anonymous
+ || name == null
+ || !name.equals(system.name)) {
+ // should never come here
+ throw new InternalError("invalid logger merge");
+ }
+ checkPermission();
+ final ConfigurationData cfg = config;
+ if (cfg != system.config) {
+ config = cfg.merge(system);
+ }
}
private void setCallerModuleRef(Module callerModule) {
@@ -408,7 +598,7 @@
// The manager field is not initialized here.
this.name = name;
this.isSystemLogger = true;
- levelValue = Level.INFO.intValue();
+ config = new ConfigurationData();
}
// It is called from LoggerContext.addLocalLogger() when the logger
@@ -451,7 +641,7 @@
private static Logger demandLogger(String name, String resourceBundleName, Class<?> caller) {
LogManager manager = LogManager.getLogManager();
if (!SystemLoggerHelper.disableCallerCheck) {
- if (caller.getClassLoader() == null) {
+ if (isSystem(caller.getModule())) {
return manager.demandSystemLogger(name, resourceBundleName, caller);
}
}
@@ -740,7 +930,7 @@
*/
public void setFilter(Filter newFilter) throws SecurityException {
checkPermission();
- filter = newFilter;
+ config.setFilter(newFilter);
}
/**
@@ -749,7 +939,7 @@
* @return a filter object (may be null)
*/
public Filter getFilter() {
- return filter;
+ return config.filter;
}
/**
@@ -765,7 +955,7 @@
if (!isLoggable(record.getLevel())) {
return;
}
- Filter theFilter = filter;
+ Filter theFilter = config.filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
@@ -784,7 +974,7 @@
}
final boolean useParentHdls = isSystemLogger
- ? logger.useParentHandlers
+ ? logger.config.useParentHandlers
: logger.getUseParentHandlers();
if (!useParentHdls) {
@@ -1804,13 +1994,13 @@
public void setLevel(Level newLevel) throws SecurityException {
checkPermission();
synchronized (treeLock) {
- levelObject = newLevel;
+ config.setLevelObject(newLevel);
updateEffectiveLevel();
}
}
final boolean isLevelInitialized() {
- return levelObject != null;
+ return config.levelObject != null;
}
/**
@@ -1821,7 +2011,7 @@
* @return this Logger's level
*/
public Level getLevel() {
- return levelObject;
+ return config.levelObject;
}
/**
@@ -1833,6 +2023,7 @@
* @return true if the given message level is currently being logged.
*/
public boolean isLoggable(Level level) {
+ int levelValue = config.levelValue;
if (level.intValue() < levelValue || levelValue == offValue) {
return false;
}
@@ -1862,7 +2053,7 @@
public void addHandler(Handler handler) throws SecurityException {
Objects.requireNonNull(handler);
checkPermission();
- handlers.add(handler);
+ config.addHandler(handler);
}
/**
@@ -1880,7 +2071,7 @@
if (handler == null) {
return;
}
- handlers.remove(handler);
+ config.removeHandler(handler);
}
/**
@@ -1895,7 +2086,7 @@
// This method should ideally be marked final - but unfortunately
// it needs to be overridden by LogManager.RootLogger
Handler[] accessCheckedHandlers() {
- return handlers.toArray(emptyHandlers);
+ return config.handlers.toArray(emptyHandlers);
}
/**
@@ -1912,7 +2103,7 @@
*/
public void setUseParentHandlers(boolean useParentHandlers) {
checkPermission();
- this.useParentHandlers = useParentHandlers;
+ config.setUseParentHandlers(useParentHandlers);
}
/**
@@ -1922,7 +2113,7 @@
* @return true if output is to be sent to the logger's parent
*/
public boolean getUseParentHandlers() {
- return useParentHandlers;
+ return config.useParentHandlers;
}
/**
@@ -2256,11 +2447,13 @@
// Figure out our current effective level.
int newLevelValue;
+ final ConfigurationData cfg = config;
+ final Level levelObject = cfg.levelObject;
if (levelObject != null) {
newLevelValue = levelObject.intValue();
} else {
if (parent != null) {
- newLevelValue = parent.levelValue;
+ newLevelValue = parent.config.levelValue;
} else {
// This may happen during initialization.
newLevelValue = Level.INFO.intValue();
@@ -2268,11 +2461,11 @@
}
// If our effective value hasn't changed, we're done.
- if (levelValue == newLevelValue) {
+ if (cfg.levelValue == newLevelValue) {
return;
}
- levelValue = newLevelValue;
+ cfg.setLevelValue(newLevelValue);
// System.err.println("effective level: \"" + getName() + "\" := " + level);
--- a/jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java Tue Jul 12 09:41:49 2016 +0800
+++ b/jdk/test/java/lang/System/Logger/default/DefaultLoggerTest.java Tue Jul 12 11:29:01 2016 +0100
@@ -539,6 +539,7 @@
throw new RuntimeException("identical loggers");
}
+ final java.util.logging.Logger sink;
final java.util.logging.Logger appSink;
final java.util.logging.Logger sysSink;
final java.util.logging.Handler appHandler;
@@ -548,10 +549,9 @@
try {
appSink = java.util.logging.Logger.getLogger("foo");
sysSink = accessSystemLogger.demandSystemLogger("foo");
- appSink.addHandler(appHandler = new MyHandler());
- sysSink.addHandler(sysHandler = new MyHandler());
- appSink.setUseParentHandlers(false);
- sysSink.setUseParentHandlers(false);
+ sink = java.util.logging.Logger.getLogger("foo");
+ sink.addHandler(appHandler = sysHandler = new MyHandler());
+ sink.setUseParentHandlers(false);
provider = LoggerFinder.getLoggerFinder();
} finally {
allowAll.get().set(false);
--- a/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java Tue Jul 12 09:41:49 2016 +0800
+++ b/jdk/test/java/lang/System/LoggerFinder/DefaultLoggerFinderTest/DefaultLoggerFinderTest.java Tue Jul 12 11:29:01 2016 +0100
@@ -299,10 +299,9 @@
final java.util.logging.Logger appSink = java.util.logging.Logger.getLogger("foo");
final java.util.logging.Logger sysSink = accessSystemLogger.demandSystemLogger("foo");
- appSink.addHandler(new MyHandler());
- sysSink.addHandler(new MyHandler());
- appSink.setUseParentHandlers(VERBOSE);
- sysSink.setUseParentHandlers(VERBOSE);
+ final java.util.logging.Logger sink = java.util.logging.Logger.getLogger("foo");
+ sink.addHandler(new MyHandler());
+ sink.setUseParentHandlers(VERBOSE);
Stream.of(args).map(TestCases::valueOf).forEach((testCase) -> {
LoggerFinder provider;
--- a/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java Tue Jul 12 09:41:49 2016 +0800
+++ b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultLoggerBridgeTest/DefaultLoggerBridgeTest.java Tue Jul 12 11:29:01 2016 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -390,6 +390,7 @@
throw new RuntimeException("identical loggers");
}
+ final java.util.logging.Logger sink;
final java.util.logging.Logger appSink;
final java.util.logging.Logger sysSink;
final MyHandler appHandler;
@@ -404,10 +405,13 @@
if (appSink == sysSink) {
throw new RuntimeException("identical backend loggers");
}
- appSink.addHandler(appHandler = new MyHandler());
- sysSink.addHandler(sysHandler = new MyHandler());
- appSink.setUseParentHandlers(VERBOSE);
- sysSink.setUseParentHandlers(VERBOSE);
+ sink = java.util.logging.Logger.getLogger("foo");
+ if (appSink != sink) {
+ throw new RuntimeException("expected same application logger");
+ }
+
+ sink.addHandler(appHandler = sysHandler = new MyHandler());
+ sink.setUseParentHandlers(VERBOSE);
} finally {
allowAll.get().set(old);
}
--- a/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java Tue Jul 12 09:41:49 2016 +0800
+++ b/jdk/test/java/lang/System/LoggerFinder/jdk/DefaultPlatformLoggerTest/DefaultPlatformLoggerTest.java Tue Jul 12 11:29:01 2016 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -42,9 +42,9 @@
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.lang.System.LoggerFinder;
+import java.util.logging.Logger;
import sun.util.logging.PlatformLogger;
import sun.util.logging.internal.LoggingProviderImpl;
-import java.lang.reflect.Module;
/**
* @test
@@ -248,10 +248,9 @@
DefaultPlatformLoggerTest.class.getModule());
java.util.logging.Logger sysSink = LoggingProviderImpl.getLogManagerAccess()
.demandLoggerFor(LogManager.getLogManager(),"foo", Thread.class.getModule());
- appSink.addHandler(new MyHandler());
- sysSink.addHandler(new MyHandler());
- appSink.setUseParentHandlers(VERBOSE);
- sysSink.setUseParentHandlers(VERBOSE);
+ java.util.logging.Logger sink = Logger.getLogger("foo");
+ sink.addHandler(new MyHandler());
+ sink.setUseParentHandlers(VERBOSE);
System.out.println("\n*** Without Security Manager\n");
test(provider, true, appSink, sysSink);
@@ -274,7 +273,7 @@
public static void test(LoggerFinder provider, boolean hasRequiredPermissions,
java.util.logging.Logger appSink, java.util.logging.Logger sysSink) throws Exception {
- // No way to giva a resource bundle to a platform logger.
+ // No way to give a resource bundle to a platform logger.
// ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
final Map<PlatformLogger, String> loggerDescMap = new HashMap<>();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/logging/SystemLoggerConfigTest.java Tue Jul 12 11:29:01 2016 +0100
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.ref.Reference;
+import java.security.Permission;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import sun.util.logging.PlatformLogger;
+
+
+/**
+ * @test
+ * @bug 8159245
+ * @summary Tests configuration of loggers.
+ * @modules java.logging/sun.util.logging.internal java.base/sun.util.logging
+ * @run main/othervm SystemLoggerConfigTest NOSECURITY
+ * @run main/othervm SystemLoggerConfigTest WITHSECURITY
+ *
+ * @author danielfuchs
+ */
+public class SystemLoggerConfigTest {
+
+ static Logger createSystemLogger(String name) {
+ return sun.util.logging.internal.LoggingProviderImpl.getLogManagerAccess()
+ .demandLoggerFor(LogManager.getLogManager(), name,
+ Thread.class.getModule());
+ }
+
+ static PlatformLogger createPlatformLogger(String name) {
+ return PlatformLogger.getLogger(name);
+ }
+
+ private static void assertFalse(boolean value, String msg) {
+ assertEquals(false, value, msg);
+ }
+ private static void assertEquals(boolean expected, boolean actual, String msg) {
+ if (expected != actual) {
+ throw new AssertionError(msg+": expected: " + expected + " actual: " + actual);
+ }
+ }
+ private static void assertEquals(int expected, int actual, String msg) {
+ if (expected != actual) {
+ throw new AssertionError(msg+": expected: " + expected + " actual: " + actual);
+ }
+ }
+ private static void assertEquals(long expected, long actual, String msg) {
+ if (expected != actual) {
+ throw new AssertionError(msg+": expected: " + expected + " actual: " + actual);
+ }
+ }
+ private static void assertEquals(Object expected, Object actual, String msg) {
+ if (!Objects.equals(expected, actual)) {
+ throw new AssertionError(msg+": expected: " + expected + " actual: " + actual);
+ }
+ }
+
+ static class TestHandler extends Handler {
+ private final List<LogRecord> records = new CopyOnWriteArrayList<>();
+ public TestHandler() {
+ super();
+ setLevel(Level.ALL);
+ }
+
+ @Override
+ public void publish(LogRecord lr) {
+ records.add(lr);
+ }
+
+ public List<LogRecord> drain() {
+ List<LogRecord> list = new ArrayList<>(records);
+ records.clear();
+ return list;
+ }
+
+ public void close() {
+ records.clear();
+ }
+
+ public void flush() {
+ }
+
+ }
+
+ public static class TestHandler1 extends TestHandler {
+ final static AtomicLong COUNT = new AtomicLong();
+ public TestHandler1() {
+ COUNT.incrementAndGet();
+ }
+ }
+
+ public static class TestHandler2 extends TestHandler {
+ final static AtomicLong COUNT = new AtomicLong();
+ public TestHandler2() {
+ COUNT.incrementAndGet();
+ }
+ }
+
+ static enum TestCase { WITHSECURITY, NOSECURITY }
+
+ public static void main(String[] args) {
+ if (args == null || args.length == 0) {
+ args = Stream.of(TestCase.values())
+ .map(String::valueOf)
+ .collect(Collectors.toList())
+ .toArray(new String[0]);
+ }
+ Stream.of(args)
+ .map(TestCase::valueOf)
+ .forEach(SystemLoggerConfigTest::launch);
+ }
+
+ public static void launch(TestCase test) {
+ switch(test) {
+ case WITHSECURITY:
+ Policy.setPolicy(new Policy() {
+ @Override
+ public boolean implies(ProtectionDomain domain, Permission permission) {
+ return true;
+ }
+ });
+ System.setSecurityManager(new SecurityManager());
+ break;
+ case NOSECURITY:
+ break;
+ default:
+ throw new InternalError("Unexpected enum: " + test);
+ }
+ try {
+ test(test.name(), ".1", ".child");
+ test(test.name(), ".2", "");
+ testUpdateConfiguration(test.name(), ".3");
+ testSetPlatformLevel(test.name(), ".4");
+ } catch (IOException io) {
+ throw new UncheckedIOException(io);
+ }
+ }
+
+ public static void test(String name, String step, String ext)
+ throws IOException {
+
+ System.out.println("\n*** Testing " + name + step + ext);
+
+ final String systemName1a = "system.logger.one.a." + name + step + ext;
+ final String systemName1b = "system.logger.one.b." + name + step + ext;
+ final String appName1a = "system.logger.one.a." + name + step;
+ final String appName1b = "system.logger.one.b." + name + step;
+ final String msg1a = "logger name: " + systemName1a;
+ final String msg1b = "logger name: " + systemName1b;
+ final String systemName2 = "system.logger.two." + name + step + ext;
+ final String appName2 = "system.logger.two." + name + step;
+ final String msg2 = "logger name: " + systemName2;
+ final String systemName3 = "system.logger.three." + name + step + ext;
+ final String appName3 = "system.logger.three." + name + step;
+ final String msg3 = "logger name: " + systemName3;
+ List<LogRecord> records;
+
+ System.out.println("\n[Case #1] Creating platform logger: " + systemName1a);
+ PlatformLogger system1a = createPlatformLogger(systemName1a);
+ System.out.println(" Creating platform logger: " + systemName1b);
+ PlatformLogger system1b = createPlatformLogger(systemName1b);
+ System.out.println(" Adding handler on root logger...");
+ TestHandler test1 = new TestHandler();
+ Logger.getLogger("").addHandler(test1);
+
+ System.out.println(" Creating and configuring app logger: " + appName1a
+ + ", " + appName1b);
+ Logger app1a = Logger.getLogger(appName1a);
+ app1a.setLevel(Level.INFO);
+ Logger app1b = Logger.getLogger(appName1b);
+ app1b.setLevel(Level.INFO);
+ assertFalse(system1a.isLoggable(PlatformLogger.Level.FINEST),
+ "Unexpected level for " + system1a);
+ System.out.println(" Configuring root logger...");
+ Logger.getLogger("").setLevel(Level.FINEST);
+ System.out.println(" Logging through system logger: " + systemName1a);
+ system1a.finest(msg1a);
+ Reference.reachabilityFence(app1a);
+ records = test1.drain();
+ assertEquals(0, records.size(), "Unexpected size for " + records.toString());
+ System.out.println(" Logging through system logger: " + systemName1b);
+ system1b.finest(msg1b);
+ Reference.reachabilityFence(app1b);
+ records = test1.drain();
+ assertEquals(0, records.size(), "Unexpected size for " + records.toString());
+ Logger.getLogger("system.logger.one.a").finest("system.logger.one.a");
+ records = test1.drain();
+ assertEquals("system.logger.one.a", records.get(0).getMessage(), "Unexpected message: ");
+ Logger.getLogger("").setLevel(Level.INFO);
+ Logger.getLogger("system.logger.one.a").finest("system.logger.one.a");
+ records = test1.drain();
+ assertEquals(0, records.size(), "Unexpected size for " + records.toString());
+
+ Reference.reachabilityFence(system1a);
+ Reference.reachabilityFence(system1b);
+
+ System.out.println("\n[Case #2] Creating system logger: " + systemName2);
+ Logger system2 = createSystemLogger(systemName2);
+ System.out.println(" Creating app logger: " + appName2);
+ Logger app2 = Logger.getLogger(appName2);
+ System.out.println(" Configuring app logger...");
+ TestHandler test2 = new TestHandler();
+ app2.setLevel(Level.ALL);
+ app2.setUseParentHandlers(false);
+ app2.addHandler(test2);
+ System.out.println(" Logging through system logger: " + systemName2);
+ system2.finest(msg2);
+ records = test2.drain();
+ assertEquals(1, records.size(), "Unexpected size for " + records.toString());
+ assertEquals(msg2, records.get(0).getMessage(), "Unexpected message: ");
+ records = test1.drain();
+ assertEquals(0, records.size(), "Unexpected size for " + records.toString());
+
+ Reference.reachabilityFence(app2);
+ Reference.reachabilityFence(system2);
+
+ System.out.println("\n[Case #3] Creating app logger: " + appName3);
+ Logger app3 = Logger.getLogger(appName3);
+ System.out.println(" Configuring app logger...");
+ TestHandler test3 = new TestHandler();
+ app3.setLevel(Level.ALL);
+ app3.setUseParentHandlers(false);
+ app3.addHandler(test3);
+ System.out.println(" Creating system logger: " + systemName3);
+ Logger system3 = createSystemLogger(systemName3);
+ System.out.println(" Logging through system logger: " + systemName3);
+ system3.finest(msg3);
+ records = test3.drain();
+ assertEquals(1, records.size(), "Unexpected size for " + records.toString());
+ assertEquals(msg3, records.get(0).getMessage(), "Unexpected message: ");
+ records = test1.drain();
+ assertEquals(0, records.size(), "Unexpected size for " + records.toString());
+
+ Reference.reachabilityFence(app3);
+ Reference.reachabilityFence(system3);
+ System.gc();
+
+ }
+
+ @SuppressWarnings("deprecated")
+ static void setPlatformLevel(PlatformLogger logger, PlatformLogger.Level level) {
+ logger.setLevel(level);
+ }
+
+ public static void testSetPlatformLevel(String name, String step) {
+ System.out.println("\n*** Testing PlatformLogger.setLevel " + name + step);
+
+ System.out.println("\n[Case #5] Creating app logger: " + name + step);
+ // this should return named logger in the global context
+ Logger foo = Logger.getLogger(name + step);
+ foo.setLevel(Level.FINE);
+
+ System.out.println(" Creating platform logger: " + name + step);
+ PlatformLogger foo1 = PlatformLogger.getLogger(name + step);
+ System.out.println(" Configuring platform logger...");
+ setPlatformLevel(foo1, PlatformLogger.Level.INFO);
+
+ System.out.println(" Checking levels...");
+ assertEquals(foo.getName(), foo1.getName(), "Bad logger names");
+ // both logger share the same config
+ assertEquals(foo.getLevel(), Level.INFO, "Bad level for user logger");
+ assertEquals(foo1.level(), PlatformLogger.Level.INFO,
+ "Bad level for platform logger");
+
+ }
+
+ static void updateConfiguration(Properties props) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ props.store(baos, "");
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ LogManager.getLogManager().updateConfiguration(bais, (k) -> (o,n) -> n != null ? n : o);
+ }
+
+ static void readConfiguration(Properties props) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ props.store(baos, "");
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ LogManager.getLogManager().readConfiguration(bais);
+ }
+
+ // Tests that though two loggers exist, only one handler is created for the
+ // pair when reading configuration.
+ //
+ public static void testUpdateConfiguration(String name, String step) throws IOException {
+
+ System.out.println("\n*** Testing LogManager.updateConfiguration " + name + step);
+
+ final String name1a = "system.logger.one.a." + name + step;
+ final String name1b = "system.logger.one.b." + name + step;
+ final String msg1a = "logger name: " + name1a;
+ final String msg1b = "logger name: " + name1b;
+ List<LogRecord> records;
+
+ TestHandler1.COUNT.set(0);
+ TestHandler2.COUNT.set(0);
+ Properties props = new Properties();
+ props.setProperty(name1a+".handlers", TestHandler1.class.getName());
+ updateConfiguration(props);
+ assertEquals(0, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(0, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ System.out.println("\n[Case #4] Creating app logger: " + name1a);
+ Logger app1a = Logger.getLogger(name1a);
+ System.out.println(" Configuring app logger...");
+ TestHandler test1 = new TestHandler();
+ app1a.setLevel(Level.ALL);
+ app1a.setUseParentHandlers(false);
+ app1a.addHandler(test1);
+ assertEquals(1, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(0, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ System.out.println(" Creating system logger: " + name1a);
+ Logger system1a = createSystemLogger(name1a);
+ assertEquals(Level.ALL, system1a.getLevel(), "Bad level for system logger " + name1a);
+ System.out.println(" Logging through system logger: " + name1a);
+ system1a.finest(msg1a);
+ records = test1.drain();
+ assertEquals(1, records.size(), "Unexpected size for " + records.toString());
+ assertEquals(msg1a, records.get(0).getMessage(), "Unexpected message: ");
+ records = test1.drain();
+ assertEquals(0, records.size(), "Unexpected size for " + records.toString());
+
+ assertEquals(1, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(0, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ props.setProperty(name1a+".handlers", TestHandler2.class.getName());
+ updateConfiguration(props);
+
+ assertEquals(1, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(1, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ updateConfiguration(props);
+
+ assertEquals(1, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(1, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ readConfiguration(props);
+
+ assertEquals(1, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ // readConfiguration reset handlers but does not recreate them
+ assertEquals(1, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ updateConfiguration(props);
+
+ assertEquals(1, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(1, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ LogManager.getLogManager().reset();
+ updateConfiguration(props);
+
+ assertEquals(1, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(2, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ props.setProperty(name1a+".handlers",
+ TestHandler2.class.getName() + "," + TestHandler1.class.getName());
+ updateConfiguration(props);
+
+ assertEquals(2, TestHandler1.COUNT.get(), "Bad instance count for "
+ + TestHandler1.class.getName());
+ assertEquals(3, TestHandler2.COUNT.get(), "Bad instance count for "
+ + TestHandler2.class.getName());
+
+ Reference.reachabilityFence(app1a);
+ Reference.reachabilityFence(system1a);
+
+ LogManager.getLogManager().readConfiguration();
+ System.gc();
+ }
+
+
+
+}