mirror of
https://github.com/openjdk/jdk.git
synced 2026-04-04 20:18:49 +00:00
8159245: Loggers created by system classes are not initialized correctly when configured programmatically from application code
Loggers of the same name now share the same configuration. Reviewed-by: mchung, mli
This commit is contained in:
parent
1931ac4196
commit
d5e84de014
@ -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 @@ public class LogManager {
|
||||
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 @@ public class LogManager {
|
||||
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 @@ public class LogManager {
|
||||
} 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;
|
||||
}
|
||||
});
|
||||
|
||||
@ -259,13 +259,185 @@ public class Logger {
|
||||
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 @@ public class Logger {
|
||||
// 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 @@ public class Logger {
|
||||
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 @@ public class Logger {
|
||||
// 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 @@ public class Logger {
|
||||
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 class Logger {
|
||||
*/
|
||||
public void setFilter(Filter newFilter) throws SecurityException {
|
||||
checkPermission();
|
||||
filter = newFilter;
|
||||
config.setFilter(newFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -749,7 +939,7 @@ public class Logger {
|
||||
* @return a filter object (may be null)
|
||||
*/
|
||||
public Filter getFilter() {
|
||||
return filter;
|
||||
return config.filter;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -765,7 +955,7 @@ public class Logger {
|
||||
if (!isLoggable(record.getLevel())) {
|
||||
return;
|
||||
}
|
||||
Filter theFilter = filter;
|
||||
Filter theFilter = config.filter;
|
||||
if (theFilter != null && !theFilter.isLoggable(record)) {
|
||||
return;
|
||||
}
|
||||
@ -784,7 +974,7 @@ public class Logger {
|
||||
}
|
||||
|
||||
final boolean useParentHdls = isSystemLogger
|
||||
? logger.useParentHandlers
|
||||
? logger.config.useParentHandlers
|
||||
: logger.getUseParentHandlers();
|
||||
|
||||
if (!useParentHdls) {
|
||||
@ -1804,13 +1994,13 @@ public class Logger {
|
||||
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 @@ public class Logger {
|
||||
* @return this Logger's level
|
||||
*/
|
||||
public Level getLevel() {
|
||||
return levelObject;
|
||||
return config.levelObject;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1833,6 +2023,7 @@ public class Logger {
|
||||
* @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 class Logger {
|
||||
public void addHandler(Handler handler) throws SecurityException {
|
||||
Objects.requireNonNull(handler);
|
||||
checkPermission();
|
||||
handlers.add(handler);
|
||||
config.addHandler(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1880,7 +2071,7 @@ public class Logger {
|
||||
if (handler == null) {
|
||||
return;
|
||||
}
|
||||
handlers.remove(handler);
|
||||
config.removeHandler(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1895,7 +2086,7 @@ public class Logger {
|
||||
// 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 class Logger {
|
||||
*/
|
||||
public void setUseParentHandlers(boolean useParentHandlers) {
|
||||
checkPermission();
|
||||
this.useParentHandlers = useParentHandlers;
|
||||
config.setUseParentHandlers(useParentHandlers);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1922,7 +2113,7 @@ public class Logger {
|
||||
* @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 @@ public class Logger {
|
||||
|
||||
// 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 @@ public class Logger {
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
@ -539,6 +539,7 @@ public class DefaultLoggerTest {
|
||||
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 @@ public class DefaultLoggerTest {
|
||||
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);
|
||||
|
||||
@ -299,10 +299,9 @@ public class DefaultLoggerFinderTest {
|
||||
|
||||
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;
|
||||
|
||||
@ -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 @@ public class DefaultLoggerBridgeTest {
|
||||
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 @@ public class DefaultLoggerBridgeTest {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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.Handler;
|
||||
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 @@ public class DefaultPlatformLoggerTest {
|
||||
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 class DefaultPlatformLoggerTest {
|
||||
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<>();
|
||||
|
||||
|
||||
423
jdk/test/java/util/logging/SystemLoggerConfigTest.java
Normal file
423
jdk/test/java/util/logging/SystemLoggerConfigTest.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user