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 */ 017package org.apache.logging.log4j.core.selector; 018 019import java.lang.ref.WeakReference; 020import java.net.URI; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.List; 025import java.util.Map; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.concurrent.ConcurrentMap; 028import java.util.concurrent.atomic.AtomicReference; 029 030import org.apache.logging.log4j.core.LoggerContext; 031import org.apache.logging.log4j.core.helpers.Loader; 032import org.apache.logging.log4j.core.impl.ContextAnchor; 033import org.apache.logging.log4j.core.impl.ReflectiveCallerClassUtility; 034import org.apache.logging.log4j.status.StatusLogger; 035 036/** 037 * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers 038 * assigned to static variables to be released along with the classes that own then. Other ContextSelectors 039 * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled. 040 * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be 041 * associated with a Class in a parent ClassLoader, which will generally have negative consequences. 042 * 043 * The main downside to this ContextSelector is that Configuration is more challenging. 044 * 045 * This ContextSelector should not be used with a Servlet Filter such as the Log4jServletFilter. 046 */ 047public class ClassLoaderContextSelector implements ContextSelector { 048 049 private static final AtomicReference<LoggerContext> CONTEXT = new AtomicReference<LoggerContext>(); 050 051 private static final PrivateSecurityManager SECURITY_MANAGER; 052 053 private static final StatusLogger LOGGER = StatusLogger.getLogger(); 054 055 private static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP = 056 new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>(); 057 058 static { 059 if (ReflectiveCallerClassUtility.isSupported()) { 060 SECURITY_MANAGER = null; 061 } else { 062 PrivateSecurityManager securityManager; 063 try { 064 securityManager = new PrivateSecurityManager(); 065 if (securityManager.getCaller(ClassLoaderContextSelector.class.getName()) == null) { 066 // This shouldn't happen. 067 securityManager = null; 068 LOGGER.error("Unable to obtain call stack from security manager."); 069 } 070 } catch (final Exception e) { 071 securityManager = null; 072 LOGGER.debug("Unable to install security manager", e); 073 } 074 SECURITY_MANAGER = securityManager; 075 } 076 } 077 078 @Override 079 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { 080 return getContext(fqcn, loader, currentContext, null); 081 } 082 083 @Override 084 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, 085 final URI configLocation) { 086 if (currentContext) { 087 final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); 088 if (ctx != null) { 089 return ctx; 090 } 091 return getDefault(); 092 } else if (loader != null) { 093 return locateContext(loader, configLocation); 094 } else { 095 if (ReflectiveCallerClassUtility.isSupported()) { 096 try { 097 Class<?> clazz = Class.class; 098 boolean next = false; 099 for (int index = 2; clazz != null; ++index) { 100 clazz = ReflectiveCallerClassUtility.getCaller(index); 101 if (clazz == null) { 102 break; 103 } 104 if (clazz.getName().equals(fqcn)) { 105 next = true; 106 continue; 107 } 108 if (next) { 109 break; 110 } 111 } 112 if (clazz != null) { 113 return locateContext(clazz.getClassLoader(), configLocation); 114 } 115 } catch (final Exception ex) { 116 // logger.debug("Unable to determine caller class via Sun Reflection", ex); 117 } 118 } 119 120 if (SECURITY_MANAGER != null) { 121 final Class<?> clazz = SECURITY_MANAGER.getCaller(fqcn); 122 if (clazz != null) { 123 final ClassLoader ldr = clazz.getClassLoader() != null ? clazz.getClassLoader() : 124 ClassLoader.getSystemClassLoader(); 125 return locateContext(ldr, configLocation); 126 } 127 } 128 129 final Throwable t = new Throwable(); 130 boolean next = false; 131 String name = null; 132 for (final StackTraceElement element : t.getStackTrace()) { 133 if (element.getClassName().equals(fqcn)) { 134 next = true; 135 continue; 136 } 137 if (next) { 138 name = element.getClassName(); 139 break; 140 } 141 } 142 if (name != null) { 143 try { 144 return locateContext(Loader.loadClass(name).getClassLoader(), configLocation); 145 } catch (final ClassNotFoundException ignore) { 146 //this is ok 147 } 148 } 149 final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get(); 150 if (lc != null) { 151 return lc; 152 } 153 return getDefault(); 154 } 155 } 156 157 @Override 158 public void removeContext(final LoggerContext context) { 159 for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) { 160 final LoggerContext ctx = entry.getValue().get().get(); 161 if (ctx == context) { 162 CONTEXT_MAP.remove(entry.getKey()); 163 } 164 } 165 } 166 167 @Override 168 public List<LoggerContext> getLoggerContexts() { 169 final List<LoggerContext> list = new ArrayList<LoggerContext>(); 170 final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values(); 171 for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) { 172 final LoggerContext ctx = ref.get().get(); 173 if (ctx != null) { 174 list.add(ctx); 175 } 176 } 177 return Collections.unmodifiableList(list); 178 } 179 180 private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) { 181 // LOG4J2-477: class loader may be null 182 final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader(); 183 final String name = loader.toString(); 184 AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name); 185 if (ref == null) { 186 if (configLocation == null) { 187 ClassLoader parent = loader.getParent(); 188 while (parent != null) { 189 190 ref = CONTEXT_MAP.get(parent.toString()); 191 if (ref != null) { 192 final WeakReference<LoggerContext> r = ref.get(); 193 final LoggerContext ctx = r.get(); 194 if (ctx != null) { 195 return ctx; 196 } 197 } 198 parent = parent.getParent(); 199 /* In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be 200 configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader. 201 In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the 202 ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader 203 that is configured by the WebAppContextListener. 204 205 ClassLoader threadLoader = null; 206 try { 207 threadLoader = Thread.currentThread().getContextClassLoader(); 208 } catch (Exception ex) { 209 // Ignore SecurityException 210 } 211 if (threadLoader != null && threadLoader == parent) { 212 break; 213 } else { 214 parent = parent.getParent(); 215 } */ 216 } 217 } 218 LoggerContext ctx = new LoggerContext(name, null, configLocation); 219 final AtomicReference<WeakReference<LoggerContext>> r = 220 new AtomicReference<WeakReference<LoggerContext>>(); 221 r.set(new WeakReference<LoggerContext>(ctx)); 222 CONTEXT_MAP.putIfAbsent(name, r); 223 ctx = CONTEXT_MAP.get(name).get().get(); 224 return ctx; 225 } 226 final WeakReference<LoggerContext> r = ref.get(); 227 LoggerContext ctx = r.get(); 228 if (ctx != null) { 229 if (ctx.getConfigLocation() == null && configLocation != null) { 230 LOGGER.debug("Setting configuration to {}", configLocation); 231 ctx.setConfigLocation(configLocation); 232 } else if (ctx.getConfigLocation() != null && configLocation != null && 233 !ctx.getConfigLocation().equals(configLocation)) { 234 LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation, 235 ctx.getConfigLocation()); 236 } 237 return ctx; 238 } 239 ctx = new LoggerContext(name, null, configLocation); 240 ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx)); 241 return ctx; 242 } 243 244 private LoggerContext getDefault() { 245 final LoggerContext ctx = CONTEXT.get(); 246 if (ctx != null) { 247 return ctx; 248 } 249 CONTEXT.compareAndSet(null, new LoggerContext("Default")); 250 return CONTEXT.get(); 251 } 252 253 /** 254 * SecurityManager that will locate the caller of the Log4j 2 API. 255 */ 256 private static class PrivateSecurityManager extends SecurityManager { 257 258 public Class<?> getCaller(final String fqcn) { 259 final Class<?>[] classes = getClassContext(); 260 boolean next = false; 261 for (final Class<?> clazz : classes) { 262 if (clazz.getName().equals(fqcn)) { 263 next = true; 264 continue; 265 } 266 if (next) { 267 return clazz; 268 } 269 } 270 return null; 271 } 272 } 273 274}