Skip to content
Snippets Groups Projects
Commit 901a68d0 authored by Tomasz Grabiec's avatar Tomasz Grabiec
Browse files

java: implement isolation of java.util.logging managers

The j.u.l framework allows only one log manager to exist. To isolate
logging configurations we need to install our own log manager which
delegates to context-local log managers.

See #172.

In order to have a fully isolated logging config system properties
need to also be isolated. This will come in the following patches.
parent ce6cc028
No related branches found
No related tags found
No related merge requests found
......@@ -89,6 +89,7 @@ int main(int argc, char **argv)
std::vector<JavaVMOption> options;
options.push_back(mkoption("-Djava.class.path=" RUNJAVA_PATH));
options.push_back(mkoption("-Djava.system.class.loader=io.osv.OsvSystemClassLoader"));
options.push_back(mkoption("-Djava.util.logging.manager=io.osv.jul.IsolatingLogManager"));
int orig_argc = argc;
int has_xms = 0, has_xmx = 0;
......
......@@ -13,6 +13,14 @@
<artifactId>runjava</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
......
package io.osv;
import io.osv.jul.LogManagerWrapper;
import io.osv.util.LazilyInitialized;
import java.util.concurrent.Callable;
/*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*
......@@ -8,6 +13,15 @@ package io.osv;
*/
public final class Context {
private final ClassLoader systemClassLoader;
private final LazilyInitialized<LogManagerWrapper> logManagerWrapper = new LazilyInitialized<>(
new Callable<LogManagerWrapper>() {
@Override
public LogManagerWrapper call() throws Exception {
return new LogManagerWrapper(Context.this);
}
});
private Thread mainThread;
public Context(ClassLoader systemClassLoader) {
......@@ -30,4 +44,8 @@ public final class Context {
public void join() throws InterruptedException {
mainThread.join();
}
public LogManagerWrapper getLogManagerWrapper() {
return logManagerWrapper.get();
}
}
package io.osv;
import io.osv.jul.IsolatingLogManager;
import java.util.logging.LogManager;
/*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*
......@@ -9,6 +13,18 @@ package io.osv;
public class ContextIsolator {
private static final ContextIsolator instance = new ContextIsolator();
static {
verifyLogManagerIsInstalled();
}
private static void verifyLogManagerIsInstalled() {
LogManager manager = LogManager.getLogManager();
if (!(manager instanceof IsolatingLogManager)) {
throw new AssertionError("For isolation to work logging manager must be "
+ IsolatingLogManager.class.getName() + " but is: " + manager.getClass().getName());
}
}
private final InheritableThreadLocal<Context> currentContext = new InheritableThreadLocal<>();
public static ContextIsolator getInstance() {
......@@ -16,8 +32,7 @@ public class ContextIsolator {
}
public ContextIsolator() {
Context mainContext = new Context(ClassLoader.getSystemClassLoader());
currentContext.set(mainContext);
currentContext.set(new Context(ClassLoader.getSystemClassLoader()));
}
public Context getContext() {
......
package io.osv.jul;
import java.util.logging.LogManager;
/*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*
* This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory.
*/
public class DefaultLogManager extends LogManager {
}
package io.osv.jul;
import io.osv.Context;
import io.osv.ContextIsolator;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.logging.LogManager;
import java.util.logging.Logger;
/*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*
* This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory.
*/
@SuppressWarnings("UnusedDeclaration")
public class IsolatingLogManager extends LogManager {
private LogManager getDelegate() {
Context context = ContextIsolator.getInstance().getContext();
return context.getLogManagerWrapper().getManager();
}
@Override
public void addPropertyChangeListener(PropertyChangeListener l) throws SecurityException {
getDelegate().addPropertyChangeListener(l);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener l) throws SecurityException {
getDelegate().removePropertyChangeListener(l);
}
@Override
public boolean addLogger(Logger logger) {
return getDelegate().addLogger(logger);
}
@Override
public Logger getLogger(String name) {
return getDelegate().getLogger(name);
}
@Override
public Enumeration<String> getLoggerNames() {
return getDelegate().getLoggerNames();
}
@Override
public void readConfiguration() throws IOException, SecurityException {
getDelegate().readConfiguration();
}
@Override
public void reset() throws SecurityException {
getDelegate().reset();
}
@Override
public void readConfiguration(InputStream ins) throws IOException, SecurityException {
getDelegate().readConfiguration(ins);
}
@Override
public String getProperty(String name) {
return getDelegate().getProperty(name);
}
@Override
public void checkAccess() throws SecurityException {
getDelegate().checkAccess();
}
}
package io.osv.jul;
import io.osv.Context;
import io.osv.util.LazilyInitialized;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class LogManagerWrapper {
private final LazilyInitialized<LogManager> managerHolder;
private final Context context;
private boolean configMayHaveChanged;
private Logger rootLogger;
public LogManagerWrapper(final Context context) {
this.context = context;
managerHolder = new LazilyInitialized<>(new Callable<LogManager>() {
@Override
public LogManager call() throws Exception {
String clazzName = context.getProperty("java.util.logging.manager");
if (clazzName == null || clazzName.equals(IsolatingLogManager.class.getName())) {
return new DefaultLogManager();
}
Class<?> clazz = context.getSystemClassLoader().loadClass(clazzName);
return (LogManager) clazz.newInstance();
}
}, new LogManagerInitializer());
}
public LogManager getManager() {
return managerHolder.get();
}
public void reconfigureRootHandlers() {
LogManager manager = getManager();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (manager) {
String handlers = manager.getProperty("handlers");
if (handlers == null) {
return;
}
for (String handlerName : handlers.split(",")) {
try {
Class clazz = context.getSystemClassLoader().loadClass(handlerName);
Handler handler = (Handler) clazz.newInstance();
String handlerLevel = manager.getProperty(handlerName + ".level");
if (handlerLevel != null) {
Level level = Level.parse(handlerLevel);
if (level == null) {
System.err.println("Failed to parse level: \"" + handlerLevel + "\" for handler " + handlerName);
} else {
handler.setLevel(level);
}
}
rootLogger.addHandler(handler);
} catch (Exception e) {
System.err.println("Failed to load handler: " + handlerName);
e.printStackTrace();
}
}
}
}
private class LogManagerInitializer implements LazilyInitialized.Initializer<LogManager> {
@Override
public void initialize(final LogManager manager) throws IOException {
// We need to assign the root logger similarly as is performed in LogManger's static block.
// The instantiated log manager may or may not use this root logger. In case it does,
// we must replicate the behavior of LogManager.RootLogger which recreates handlers every
// time configuration is re-read.
//
// However, if this default root logger will not be used we
// must not attempt to parse the config because it may be of different format. Tomcat
// for example is using different syntax for "handlers" property. Therefore our root
// logger must re-read the config only when asked to, lazily.
rootLogger = createDefaultRootLogger();
manager.addLogger(rootLogger);
manager.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
synchronized (manager) {
configMayHaveChanged = true;
}
}
});
manager.readConfiguration();
}
private Logger createDefaultRootLogger() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Logger.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
synchronized (getManager()) {
if (configMayHaveChanged) {
configMayHaveChanged = false;
reconfigureRootHandlers();
}
}
return methodProxy.invokeSuper(o, objects);
}
});
return (Logger) enhancer.create(new Class[]{String.class, String.class}, new Object[]{"", null});
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment