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.impl.ContextAnchor; 032import org.apache.logging.log4j.spi.LoggerContextShutdownAware; 033import org.apache.logging.log4j.status.StatusLogger; 034import org.apache.logging.log4j.util.StackLocatorUtil; 035 036/** 037 * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers assigned 038 * to static variables to be released along with the classes that own then. Other ContextSelectors will generally cause 039 * Loggers associated with classes loaded from different ClassLoaders to be co-mingled. This is a problem if, for 040 * example, a web application is undeployed as some of the Loggers being released may be associated with a Class in a 041 * 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, LoggerContextShutdownAware { 048 049 private static final AtomicReference<LoggerContext> DEFAULT_CONTEXT = new AtomicReference<>(); 050 051 protected static final StatusLogger LOGGER = StatusLogger.getLogger(); 052 053 protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP = 054 new ConcurrentHashMap<>(); 055 056 @Override 057 public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext loggerContext) { 058 if (loggerContext instanceof LoggerContext) { 059 removeContext((LoggerContext) loggerContext); 060 } 061 } 062 063 @Override 064 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) { 065 return getContext(fqcn, loader, currentContext, null); 066 } 067 068 @Override 069 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext, 070 final 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 final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn); 081 if (clazz != null) { 082 return locateContext(clazz.getClassLoader(), configLocation); 083 } 084 final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get(); 085 if (lc != null) { 086 return lc; 087 } 088 return getDefault(); 089 } 090 } 091 092 @Override 093 public void removeContext(final LoggerContext context) { 094 for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) { 095 final LoggerContext ctx = entry.getValue().get().get(); 096 if (ctx == context) { 097 CONTEXT_MAP.remove(entry.getKey()); 098 } 099 } 100 } 101 102 @Override 103 public List<LoggerContext> getLoggerContexts() { 104 final List<LoggerContext> list = new ArrayList<>(); 105 final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values(); 106 for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) { 107 final LoggerContext ctx = ref.get().get(); 108 if (ctx != null) { 109 list.add(ctx); 110 } 111 } 112 return Collections.unmodifiableList(list); 113 } 114 115 private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) { 116 // LOG4J2-477: class loader may be null 117 final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader(); 118 final String name = toContextMapKey(loader); 119 AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name); 120 if (ref == null) { 121 if (configLocation == null) { 122 ClassLoader parent = loader.getParent(); 123 while (parent != null) { 124 125 ref = CONTEXT_MAP.get(toContextMapKey(parent)); 126 if (ref != null) { 127 final WeakReference<LoggerContext> r = ref.get(); 128 final LoggerContext ctx = r.get(); 129 if (ctx != null) { 130 return ctx; 131 } 132 } 133 parent = parent.getParent(); 134 /* In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be 135 configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader. 136 In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the 137 ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader 138 that is configured by the WebAppContextListener. 139 140 ClassLoader threadLoader = null; 141 try { 142 threadLoader = Thread.currentThread().getContextClassLoader(); 143 } catch (Exception ex) { 144 // Ignore SecurityException 145 } 146 if (threadLoader != null && threadLoader == parent) { 147 break; 148 } else { 149 parent = parent.getParent(); 150 } */ 151 } 152 } 153 LoggerContext ctx = createContext(name, configLocation); 154 final AtomicReference<WeakReference<LoggerContext>> r = new AtomicReference<>(); 155 r.set(new WeakReference<>(ctx)); 156 CONTEXT_MAP.putIfAbsent(name, r); 157 ctx = CONTEXT_MAP.get(name).get().get(); 158 return ctx; 159 } 160 final WeakReference<LoggerContext> weakRef = ref.get(); 161 LoggerContext ctx = weakRef.get(); 162 if (ctx != null) { 163 if (ctx.getConfigLocation() == null && configLocation != null) { 164 LOGGER.debug("Setting configuration to {}", configLocation); 165 ctx.setConfigLocation(configLocation); 166 } else if (ctx.getConfigLocation() != null && configLocation != null 167 && !ctx.getConfigLocation().equals(configLocation)) { 168 LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation, 169 ctx.getConfigLocation()); 170 } 171 return ctx; 172 } 173 ctx = createContext(name, configLocation); 174 ref.compareAndSet(weakRef, new WeakReference<>(ctx)); 175 return ctx; 176 } 177 178 protected LoggerContext createContext(final String name, final URI configLocation) { 179 return new LoggerContext(name, null, configLocation); 180 } 181 182 protected String toContextMapKey(final ClassLoader loader) { 183 return Integer.toHexString(System.identityHashCode(loader)); 184 } 185 186 protected LoggerContext getDefault() { 187 final LoggerContext ctx = DEFAULT_CONTEXT.get(); 188 if (ctx != null) { 189 return ctx; 190 } 191 DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null)); 192 return DEFAULT_CONTEXT.get(); 193 } 194 195 protected String defaultContextName() { 196 return "Default"; 197 } 198}