blob: feaa216831a52dc670838083c40cef3430d4b15e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache license, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the license for the specific language governing permissions and
* limitations under the license.
*/
package org.apache.logging.log4j.core.selector;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.helpers.Loader;
import org.apache.logging.log4j.core.javaee.ContextAnchor;
import org.apache.logging.log4j.status.StatusLogger;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers
* assigned to static variables to be released along with the classes that own then. Other ContextSelectors
* will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled.
* This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be
* associated with a Class in a parent ClassLoader, which will generally have negative consequences.
*
* The main downside to this ContextSelector is that Configuration is more challenging.
*
* This ContextSelector should not be used with a Servlet Filter such as the JNDIContextFilter.
*/
public class ClassLoaderContextSelector implements ContextSelector {
private static AtomicReference<LoggerContext> context = new AtomicReference<LoggerContext>();
private static PrivateSecurityManager securityManager;
private static Method getCallerClass;
private static StatusLogger logger = StatusLogger.getLogger();
private static ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> contextMap =
new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
static {
setupCallerCheck();
}
public LoggerContext getContext(String fqcn, boolean currentContext) {
if (currentContext) {
LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
if (ctx != null) {
return ctx;
}
return getDefault();
} else {
if (getCallerClass != null) {
try {
Class clazz = Class.class;
boolean next = false;
for (int index = 2; clazz != null; ++index) {
Object[] params = new Object[] {index};
clazz = (Class) getCallerClass.invoke(null, params);
if (clazz == null) {
break;
}
if (clazz.getName().equals(fqcn)) {
next = true;
continue;
}
if (next) {
break;
}
}
if (clazz != null) {
return locateContext(clazz.getClassLoader(), null);
}
} catch (Exception ex) {
// logger.debug("Unable to determine caller class via Sun Reflection", ex);
}
}
if (securityManager != null) {
Class clazz = securityManager.getCaller(fqcn);
return locateContext(clazz.getClassLoader(), null);
}
Throwable t = new Throwable();
boolean next = false;
String name = null;
for (StackTraceElement element : t.getStackTrace()) {
if (element.getClassName().equals(fqcn)) {
next = true;
continue;
}
if (next) {
name = element.getClassName();
break;
}
}
if (name != null) {
try {
return locateContext(Loader.loadClass(name).getClassLoader(), null);
} catch (ClassNotFoundException ex) {
//System.out.println("Could not load class " + name);
}
}
LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
if (lc != null) {
return lc;
}
return getDefault();
}
}
public void removeContext(LoggerContext context) {
for (Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : contextMap.entrySet()) {
LoggerContext ctx = entry.getValue().get().get();
if (ctx == context) {
contextMap.remove(entry.getKey());
}
}
}
public List<LoggerContext> getLoggerContexts() {
List<LoggerContext> list = new ArrayList<LoggerContext>();
Collection<AtomicReference<WeakReference<LoggerContext>>> coll = contextMap.values();
for (AtomicReference<WeakReference<LoggerContext>> ref : coll) {
LoggerContext ctx = ref.get().get();
if (ctx != null) {
list.add(ctx);
}
}
return Collections.unmodifiableList(list);
}
private LoggerContext locateContext(ClassLoader loader, String configLocation) {
String name = loader.toString();
AtomicReference<WeakReference<LoggerContext>> ref = contextMap.get(name);
if (ref == null) {
LoggerContext ctx = new LoggerContext(name, null, configLocation);
AtomicReference<WeakReference<LoggerContext>> r =
new AtomicReference<WeakReference<LoggerContext>>();
r.set(new WeakReference<LoggerContext>(ctx));
contextMap.putIfAbsent(loader.toString(), r);
ctx = contextMap.get(name).get().get();
return ctx;
} else {
WeakReference<LoggerContext> r = ref.get();
LoggerContext ctx = r.get();
if (ctx != null) {
return ctx;
}
ctx = new LoggerContext(name, null, configLocation);
ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx));
return ctx;
}
}
private static void setupCallerCheck() {
try {
ClassLoader loader = Loader.getClassLoader();
Class clazz = loader.loadClass("sun.reflect.Reflection");
Method[] methods = clazz.getMethods();
for (Method method : methods) {
int modifier = method.getModifiers();
if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) {
getCallerClass = method;
break;
}
}
} catch (ClassNotFoundException cnfe) {
logger.debug("sun.reflect.Reflection is not installed");
}
try {
securityManager = new PrivateSecurityManager();
} catch (Exception ex) {
ex.printStackTrace();
logger.debug("Unable to install security manager", ex);
}
}
private LoggerContext getDefault() {
LoggerContext ctx = context.get();
if (ctx != null) {
return ctx;
}
context.compareAndSet(null, new LoggerContext("Default"));
return context.get();
}
/**
* SecurityManager that will locate the caller of the Log4j2 API.
*/
private static class PrivateSecurityManager extends SecurityManager {
public Class getCaller(String fqcn) {
Class[] classes = getClassContext();
boolean next = false;
for (Class clazz : classes) {
if (clazz.getName().equals(fqcn)) {
next = true;
continue;
}
if (next) {
return clazz;
}
}
return null;
}
}
}