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 org.apache.logging.log4j.core.LoggerContext; 020 import org.apache.logging.log4j.core.helpers.Loader; 021 import org.apache.logging.log4j.core.impl.ContextAnchor; 022 import org.apache.logging.log4j.status.StatusLogger; 023 024 import java.lang.ref.WeakReference; 025 import java.lang.reflect.Method; 026 import java.lang.reflect.Modifier; 027 import java.net.URI; 028 import java.util.ArrayList; 029 import java.util.Collection; 030 import java.util.Collections; 031 import java.util.List; 032 import java.util.Map; 033 import java.util.concurrent.ConcurrentHashMap; 034 import java.util.concurrent.ConcurrentMap; 035 import java.util.concurrent.atomic.AtomicReference; 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 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { 066 return getContext(fqcn, loader, currentContext, null); 067 } 068 069 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, 070 URI configLocation) { 071 if (currentContext) { 072 final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); 073 if (ctx != null) { 074 return ctx; 075 } 076 return getDefault(); 077 } else if (loader != null) { 078 return locateContext(loader, configLocation); 079 } else { 080 if (getCallerClass != null) { 081 try { 082 Class clazz = Class.class; 083 boolean next = false; 084 for (int index = 2; clazz != null; ++index) { 085 final Object[] params = new Object[] {index}; 086 clazz = (Class) getCallerClass.invoke(null, params); 087 if (clazz == null) { 088 break; 089 } 090 if (clazz.getName().equals(fqcn)) { 091 next = true; 092 continue; 093 } 094 if (next) { 095 break; 096 } 097 } 098 if (clazz != null) { 099 return locateContext(clazz.getClassLoader(), configLocation); 100 } 101 } catch (final Exception ex) { 102 // logger.debug("Unable to determine caller class via Sun Reflection", ex); 103 } 104 } 105 106 if (securityManager != null) { 107 final Class clazz = securityManager.getCaller(fqcn); 108 if (clazz != null) { 109 final ClassLoader ldr = clazz.getClassLoader() != null ? clazz.getClassLoader() : 110 ClassLoader.getSystemClassLoader(); 111 return locateContext(ldr, configLocation); 112 } 113 } 114 115 final Throwable t = new Throwable(); 116 boolean next = false; 117 String name = null; 118 for (final StackTraceElement element : t.getStackTrace()) { 119 if (element.getClassName().equals(fqcn)) { 120 next = true; 121 continue; 122 } 123 if (next) { 124 name = element.getClassName(); 125 break; 126 } 127 } 128 if (name != null) { 129 try { 130 return locateContext(Loader.loadClass(name).getClassLoader(), configLocation); 131 } catch (final ClassNotFoundException ex) { 132 //System.out.println("Could not load class " + name); 133 } 134 } 135 final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get(); 136 if (lc != null) { 137 return lc; 138 } 139 return getDefault(); 140 } 141 } 142 143 public void removeContext(final LoggerContext context) { 144 for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) { 145 final LoggerContext ctx = entry.getValue().get().get(); 146 if (ctx == context) { 147 CONTEXT_MAP.remove(entry.getKey()); 148 } 149 } 150 } 151 152 public List<LoggerContext> getLoggerContexts() { 153 final List<LoggerContext> list = new ArrayList<LoggerContext>(); 154 final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values(); 155 for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) { 156 final LoggerContext ctx = ref.get().get(); 157 if (ctx != null) { 158 list.add(ctx); 159 } 160 } 161 return Collections.unmodifiableList(list); 162 } 163 164 private LoggerContext locateContext(final ClassLoader loader, final URI configLocation) { 165 final String name = loader.toString(); 166 AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name); 167 if (ref == null) { 168 if (configLocation == null) { 169 ClassLoader parent = loader.getParent(); 170 while (parent != null) { 171 172 ref = CONTEXT_MAP.get(parent.toString()); 173 if (ref != null) { 174 final WeakReference<LoggerContext> r = ref.get(); 175 LoggerContext ctx = r.get(); 176 if (ctx != null) { 177 return ctx; 178 } 179 } 180 parent = parent.getParent(); 181 /* In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be 182 configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader. 183 In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the 184 ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader 185 that is configured by the WebAppContextListener. 186 187 ClassLoader threadLoader = null; 188 try { 189 threadLoader = Thread.currentThread().getContextClassLoader(); 190 } catch (Exception ex) { 191 // Ignore SecurityException 192 } 193 if (threadLoader != null && threadLoader == parent) { 194 break; 195 } else { 196 parent = parent.getParent(); 197 } */ 198 } 199 } 200 LoggerContext ctx = new LoggerContext(name, null, configLocation); 201 final AtomicReference<WeakReference<LoggerContext>> r = 202 new AtomicReference<WeakReference<LoggerContext>>(); 203 r.set(new WeakReference<LoggerContext>(ctx)); 204 CONTEXT_MAP.putIfAbsent(loader.toString(), r); 205 ctx = CONTEXT_MAP.get(name).get().get(); 206 return ctx; 207 } else { 208 final WeakReference<LoggerContext> r = ref.get(); 209 LoggerContext ctx = r.get(); 210 if (ctx != null) { 211 return ctx; 212 } 213 ctx = new LoggerContext(name, null, configLocation); 214 ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx)); 215 return ctx; 216 } 217 } 218 219 private static void setupCallerCheck() { 220 try { 221 final ClassLoader loader = Loader.getClassLoader(); 222 final Class clazz = loader.loadClass("sun.reflect.Reflection"); 223 final Method[] methods = clazz.getMethods(); 224 for (final Method method : methods) { 225 final int modifier = method.getModifiers(); 226 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) { 227 getCallerClass = method; 228 break; 229 } 230 } 231 } catch (final ClassNotFoundException cnfe) { 232 LOGGER.debug("sun.reflect.Reflection is not installed"); 233 } 234 try { 235 securityManager = new PrivateSecurityManager(); 236 } catch (final Exception ex) { 237 ex.printStackTrace(); 238 LOGGER.debug("Unable to install security manager", ex); 239 } 240 } 241 242 private LoggerContext getDefault() { 243 final LoggerContext ctx = CONTEXT.get(); 244 if (ctx != null) { 245 return ctx; 246 } 247 CONTEXT.compareAndSet(null, new LoggerContext("Default")); 248 return CONTEXT.get(); 249 } 250 251 /** 252 * SecurityManager that will locate the caller of the Log4j2 API. 253 */ 254 private static class PrivateSecurityManager extends SecurityManager { 255 256 public Class getCaller(final String fqcn) { 257 final Class[] classes = getClassContext(); 258 boolean next = false; 259 for (final Class clazz : classes) { 260 if (clazz.getName().equals(fqcn)) { 261 next = true; 262 continue; 263 } 264 if (next) { 265 return clazz; 266 } 267 } 268 return null; 269 } 270 } 271 272 }