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.TimeUnit; 029import java.util.concurrent.atomic.AtomicReference; 030 031import org.apache.logging.log4j.core.LoggerContext; 032import org.apache.logging.log4j.core.impl.ContextAnchor; 033import org.apache.logging.log4j.spi.LoggerContextShutdownAware; 034import org.apache.logging.log4j.status.StatusLogger; 035import org.apache.logging.log4j.util.StackLocatorUtil; 036 037/** 038 * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers assigned 039 * to static variables to be released along with the classes that own then. Other ContextSelectors will generally cause 040 * Loggers associated with classes loaded from different ClassLoaders to be co-mingled. This is a problem if, for 041 * example, a web application is undeployed as some of the Loggers being released may be associated with a Class in a 042 * 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 Log4jServletFilter. 047 */ 048public class ClassLoaderContextSelector implements ContextSelector, LoggerContextShutdownAware { 049 050 private static final AtomicReference<LoggerContext> DEFAULT_CONTEXT = new AtomicReference<>(); 051 052 protected static final StatusLogger LOGGER = StatusLogger.getLogger(); 053 054 protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP = 055 new ConcurrentHashMap<>(); 056 057 @Override 058 public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext, 059 final boolean allContexts) { 060 LoggerContext ctx = null; 061 if (currentContext) { 062 ctx = ContextAnchor.THREAD_CONTEXT.get(); 063 } else if (loader != null) { 064 ctx = findContext(loader); 065 } else { 066 final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn); 067 if (clazz != null) { 068 ctx = findContext(clazz.getClassLoader()); 069 } 070 if (ctx == null) { 071 ctx = ContextAnchor.THREAD_CONTEXT.get(); 072 } 073 } 074 if (ctx != null) { 075 ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS); 076 } 077 } 078 079 @Override 080 public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext loggerContext) { 081 if (loggerContext instanceof LoggerContext) { 082 removeContext((LoggerContext) loggerContext); 083 } 084 } 085 086 @Override 087 public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { 088 LoggerContext ctx; 089 if (currentContext) { 090 ctx = ContextAnchor.THREAD_CONTEXT.get(); 091 } else if (loader != null) { 092 ctx = findContext(loader); 093 } else { 094 final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn); 095 if (clazz != null) { 096 ctx = findContext(clazz.getClassLoader()); 097 } else { 098 ctx = ContextAnchor.THREAD_CONTEXT.get(); 099 } 100 } 101 return ctx != null && ctx.isStarted(); 102 } 103 104 private LoggerContext findContext(ClassLoader loaderOrNull) { 105 final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader(); 106 final String name = toContextMapKey(loader); 107 AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name); 108 if (ref != null) { 109 final WeakReference<LoggerContext> weakRef = ref.get(); 110 return weakRef.get(); 111 } 112 return null; 113 } 114 115 @Override 116 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { 117 return getContext(fqcn, loader, currentContext, null); 118 } 119 120 @Override 121 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, 122 final URI configLocation) { 123 return getContext(fqcn, loader, null, currentContext, configLocation); 124 } 125 126 @Override 127 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Map.Entry<String, Object> entry, 128 final boolean currentContext, final URI configLocation) { 129 if (currentContext) { 130 final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); 131 if (ctx != null) { 132 return ctx; 133 } 134 return getDefault(); 135 } else if (loader != null) { 136 return locateContext(loader, entry, configLocation); 137 } else { 138 final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn); 139 if (clazz != null) { 140 return locateContext(clazz.getClassLoader(), entry, configLocation); 141 } 142 final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get(); 143 if (lc != null) { 144 return lc; 145 } 146 return getDefault(); 147 } 148 } 149 150 @Override 151 public void removeContext(final LoggerContext context) { 152 for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) { 153 final LoggerContext ctx = entry.getValue().get().get(); 154 if (ctx == context) { 155 CONTEXT_MAP.remove(entry.getKey()); 156 } 157 } 158 } 159 160 @Override 161 public boolean isClassLoaderDependent() { 162 // By definition the ClassLoaderContextSelector depends on the callers class loader. 163 return true; 164 } 165 166 @Override 167 public List<LoggerContext> getLoggerContexts() { 168 final List<LoggerContext> list = new ArrayList<>(); 169 final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values(); 170 for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) { 171 final LoggerContext ctx = ref.get().get(); 172 if (ctx != null) { 173 list.add(ctx); 174 } 175 } 176 return Collections.unmodifiableList(list); 177 } 178 179 private LoggerContext locateContext(final ClassLoader loaderOrNull, final Map.Entry<String, Object> entry, 180 final URI configLocation) { 181 // LOG4J2-477: class loader may be null 182 final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader(); 183 final String name = toContextMapKey(loader); 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(toContextMapKey(parent)); 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 = createContext(name, configLocation); 219 if (entry != null) { 220 ctx.putObject(entry.getKey(), entry.getValue()); 221 } 222 LoggerContext newContext = CONTEXT_MAP.computeIfAbsent(name, 223 k -> new AtomicReference<>(new WeakReference<>(ctx))).get().get(); 224 if (newContext == ctx) { 225 ctx.addShutdownListener(this); 226 } 227 return newContext; 228 } 229 final WeakReference<LoggerContext> weakRef = ref.get(); 230 LoggerContext ctx = weakRef.get(); 231 if (ctx != null) { 232 if (entry != null && ctx.getObject(entry.getKey()) == null) { 233 ctx.putObject(entry.getKey(), entry.getValue()); 234 } 235 if (ctx.getConfigLocation() == null && configLocation != null) { 236 LOGGER.debug("Setting configuration to {}", configLocation); 237 ctx.setConfigLocation(configLocation); 238 } else if (ctx.getConfigLocation() != null && configLocation != null 239 && !ctx.getConfigLocation().equals(configLocation)) { 240 LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation, 241 ctx.getConfigLocation()); 242 } 243 return ctx; 244 } 245 ctx = createContext(name, configLocation); 246 if (entry != null) { 247 ctx.putObject(entry.getKey(), entry.getValue()); 248 } 249 ref.compareAndSet(weakRef, new WeakReference<>(ctx)); 250 return ctx; 251 } 252 253 protected LoggerContext createContext(final String name, final URI configLocation) { 254 return new LoggerContext(name, null, configLocation); 255 } 256 257 protected String toContextMapKey(final ClassLoader loader) { 258 return Integer.toHexString(System.identityHashCode(loader)); 259 } 260 261 protected LoggerContext getDefault() { 262 final LoggerContext ctx = DEFAULT_CONTEXT.get(); 263 if (ctx != null) { 264 return ctx; 265 } 266 DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null)); 267 return DEFAULT_CONTEXT.get(); 268 } 269 270 protected String defaultContextName() { 271 return "Default"; 272 } 273}