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