001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.selector; 018 019 import java.lang.ref.WeakReference; 020 import java.lang.reflect.Method; 021 import java.lang.reflect.Modifier; 022 import java.net.URI; 023 import java.util.ArrayList; 024 import java.util.Collection; 025 import java.util.Collections; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.concurrent.ConcurrentHashMap; 029 import java.util.concurrent.ConcurrentMap; 030 import java.util.concurrent.atomic.AtomicReference; 031 032 import org.apache.logging.log4j.core.LoggerContext; 033 import org.apache.logging.log4j.core.helpers.Loader; 034 import org.apache.logging.log4j.core.impl.ContextAnchor; 035 import org.apache.logging.log4j.status.StatusLogger; 036 037 /** 038 * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers 039 * assigned to static variables to be released along with the classes that own then. Other ContextSelectors 040 * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled. 041 * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be 042 * associated with a Class in a parent ClassLoader, which will generally have negative consequences. 043 * 044 * The main downside to this ContextSelector is that Configuration is more challenging. 045 * 046 * This ContextSelector should not be used with a Servlet Filter such as the JNDIContextFilter. 047 */ 048 public class ClassLoaderContextSelector implements ContextSelector { 049 050 private static final AtomicReference<LoggerContext> CONTEXT = new AtomicReference<LoggerContext>(); 051 052 private static PrivateSecurityManager securityManager; 053 054 private static Method getCallerClass; 055 056 private static final StatusLogger LOGGER = StatusLogger.getLogger(); 057 058 private static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP = 059 new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>(); 060 061 static { 062 setupCallerCheck(); 063 } 064 065 @Override 066 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { 067 return getContext(fqcn, loader, currentContext, null); 068 } 069 070 @Override 071 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, 072 URI configLocation) { 073 if (currentContext) { 074 final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); 075 if (ctx != null) { 076 return ctx; 077 } 078 return getDefault(); 079 } else if (loader != null) { 080 return locateContext(loader, configLocation); 081 } else { 082 if (getCallerClass != null) { 083 try { 084 Class clazz = Class.class; 085 boolean next = false; 086 for (int index = 2; clazz != null; ++index) { 087 final Object[] params = new Object[] {index}; 088 clazz = (Class) getCallerClass.invoke(null, params); 089 if (clazz == null) { 090 break; 091 } 092 if (clazz.getName().equals(fqcn)) { 093 next = true; 094 continue; 095 } 096 if (next) { 097 break; 098 } 099 } 100 if (clazz != null) { 101 return locateContext(clazz.getClassLoader(), configLocation); 102 } 103 } catch (final Exception ex) { 104 // logger.debug("Unable to determine caller class via Sun Reflection", ex); 105 } 106 } 107 108 if (securityManager != null) { 109 final Class clazz = securityManager.getCaller(fqcn); 110 if (clazz != null) { 111 final ClassLoader ldr = clazz.getClassLoader() != null ? clazz.getClassLoader() : 112 ClassLoader.getSystemClassLoader(); 113 return locateContext(ldr, configLocation); 114 } 115 } 116 117 final Throwable t = new Throwable(); 118 boolean next = false; 119 String name = null; 120 for (final StackTraceElement element : t.getStackTrace()) { 121 if (element.getClassName().equals(fqcn)) { 122 next = true; 123 continue; 124 } 125 if (next) { 126 name = element.getClassName(); 127 break; 128 } 129 } 130 if (name != null) { 131 try { 132 return locateContext(Loader.loadClass(name).getClassLoader(), configLocation); 133 } catch (final ClassNotFoundException ex) { 134 //System.out.println("Could not load class " + name); 135 } 136 } 137 final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get(); 138 if (lc != null) { 139 return lc; 140 } 141 return getDefault(); 142 } 143 } 144 145 @Override 146 public void removeContext(final LoggerContext context) { 147 for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) { 148 final LoggerContext ctx = entry.getValue().get().get(); 149 if (ctx == context) { 150 CONTEXT_MAP.remove(entry.getKey()); 151 } 152 } 153 } 154 155 @Override 156 public List<LoggerContext> getLoggerContexts() { 157 final List<LoggerContext> list = new ArrayList<LoggerContext>(); 158 final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values(); 159 for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) { 160 final LoggerContext ctx = ref.get().get(); 161 if (ctx != null) { 162 list.add(ctx); 163 } 164 } 165 return Collections.unmodifiableList(list); 166 } 167 168 private LoggerContext locateContext(final ClassLoader loader, final URI configLocation) { 169 final String name = loader.toString(); 170 AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name); 171 if (ref == null) { 172 if (configLocation == null) { 173 ClassLoader parent = loader.getParent(); 174 while (parent != null) { 175 176 ref = CONTEXT_MAP.get(parent.toString()); 177 if (ref != null) { 178 final WeakReference<LoggerContext> r = ref.get(); 179 LoggerContext ctx = r.get(); 180 if (ctx != null) { 181 return ctx; 182 } 183 } 184 parent = parent.getParent(); 185 /* In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be 186 configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader. 187 In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the 188 ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader 189 that is configured by the WebAppContextListener. 190 191 ClassLoader threadLoader = null; 192 try { 193 threadLoader = Thread.currentThread().getContextClassLoader(); 194 } catch (Exception ex) { 195 // Ignore SecurityException 196 } 197 if (threadLoader != null && threadLoader == parent) { 198 break; 199 } else { 200 parent = parent.getParent(); 201 } */ 202 } 203 } 204 LoggerContext ctx = new LoggerContext(name, null, configLocation); 205 final AtomicReference<WeakReference<LoggerContext>> r = 206 new AtomicReference<WeakReference<LoggerContext>>(); 207 r.set(new WeakReference<LoggerContext>(ctx)); 208 CONTEXT_MAP.putIfAbsent(loader.toString(), r); 209 ctx = CONTEXT_MAP.get(name).get().get(); 210 return ctx; 211 } else { 212 final WeakReference<LoggerContext> r = ref.get(); 213 LoggerContext ctx = r.get(); 214 if (ctx != null) { 215 return ctx; 216 } 217 ctx = new LoggerContext(name, null, configLocation); 218 ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx)); 219 return ctx; 220 } 221 } 222 223 private static void setupCallerCheck() { 224 try { 225 final ClassLoader loader = Loader.getClassLoader(); 226 final Class clazz = loader.loadClass("sun.reflect.Reflection"); 227 final Method[] methods = clazz.getMethods(); 228 for (final Method method : methods) { 229 final int modifier = method.getModifiers(); 230 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) { 231 getCallerClass = method; 232 break; 233 } 234 } 235 } catch (final ClassNotFoundException cnfe) { 236 LOGGER.debug("sun.reflect.Reflection is not installed"); 237 } 238 try { 239 securityManager = new PrivateSecurityManager(); 240 } catch (final Exception ex) { 241 ex.printStackTrace(); 242 LOGGER.debug("Unable to install security manager", ex); 243 } 244 } 245 246 private LoggerContext getDefault() { 247 final LoggerContext ctx = CONTEXT.get(); 248 if (ctx != null) { 249 return ctx; 250 } 251 CONTEXT.compareAndSet(null, new LoggerContext("Default")); 252 return CONTEXT.get(); 253 } 254 255 /** 256 * SecurityManager that will locate the caller of the Log4j 2 API. 257 */ 258 private static class PrivateSecurityManager extends SecurityManager { 259 260 public Class getCaller(final String fqcn) { 261 final Class[] classes = getClassContext(); 262 boolean next = false; 263 for (final Class clazz : classes) { 264 if (clazz.getName().equals(fqcn)) { 265 next = true; 266 continue; 267 } 268 if (next) { 269 return clazz; 270 } 271 } 272 return null; 273 } 274 } 275 276 }