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.util.ArrayList; 028 import java.util.Collection; 029 import java.util.Collections; 030 import java.util.List; 031 import java.util.Map; 032 import java.util.concurrent.ConcurrentHashMap; 033 import java.util.concurrent.ConcurrentMap; 034 import java.util.concurrent.atomic.AtomicReference; 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 JNDIContextFilter. 046 */ 047 public class ClassLoaderContextSelector implements ContextSelector { 048 049 private static final AtomicReference<LoggerContext> context = new AtomicReference<LoggerContext>(); 050 051 private static PrivateSecurityManager securityManager; 052 053 private static Method getCallerClass; 054 055 private static final StatusLogger logger = StatusLogger.getLogger(); 056 057 private static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> contextMap = 058 new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>(); 059 060 static { 061 setupCallerCheck(); 062 } 063 064 public LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext) { 065 if (currentContext) { 066 LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get(); 067 if (ctx != null) { 068 return ctx; 069 } 070 return getDefault(); 071 } else if (loader != null) { 072 return locateContext(loader, null); 073 } else { 074 if (getCallerClass != null) { 075 try { 076 Class clazz = Class.class; 077 boolean next = false; 078 for (int index = 2; clazz != null; ++index) { 079 Object[] params = new Object[] {index}; 080 clazz = (Class) getCallerClass.invoke(null, params); 081 if (clazz == null) { 082 break; 083 } 084 if (clazz.getName().equals(fqcn)) { 085 next = true; 086 continue; 087 } 088 if (next) { 089 break; 090 } 091 } 092 if (clazz != null) { 093 return locateContext(clazz.getClassLoader(), null); 094 } 095 } catch (Exception ex) { 096 // logger.debug("Unable to determine caller class via Sun Reflection", ex); 097 } 098 } 099 100 if (securityManager != null) { 101 Class clazz = securityManager.getCaller(fqcn); 102 if (clazz != null) { 103 return locateContext(clazz.getClassLoader(), null); 104 } 105 } 106 107 Throwable t = new Throwable(); 108 boolean next = false; 109 String name = null; 110 for (StackTraceElement element : t.getStackTrace()) { 111 if (element.getClassName().equals(fqcn)) { 112 next = true; 113 continue; 114 } 115 if (next) { 116 name = element.getClassName(); 117 break; 118 } 119 } 120 if (name != null) { 121 try { 122 return locateContext(Loader.loadClass(name).getClassLoader(), null); 123 } catch (ClassNotFoundException ex) { 124 //System.out.println("Could not load class " + name); 125 } 126 } 127 LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get(); 128 if (lc != null) { 129 return lc; 130 } 131 return getDefault(); 132 } 133 } 134 135 public void removeContext(LoggerContext context) { 136 for (Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : contextMap.entrySet()) { 137 LoggerContext ctx = entry.getValue().get().get(); 138 if (ctx == context) { 139 contextMap.remove(entry.getKey()); 140 } 141 } 142 } 143 144 public List<LoggerContext> getLoggerContexts() { 145 List<LoggerContext> list = new ArrayList<LoggerContext>(); 146 Collection<AtomicReference<WeakReference<LoggerContext>>> coll = contextMap.values(); 147 for (AtomicReference<WeakReference<LoggerContext>> ref : coll) { 148 LoggerContext ctx = ref.get().get(); 149 if (ctx != null) { 150 list.add(ctx); 151 } 152 } 153 return Collections.unmodifiableList(list); 154 } 155 156 private LoggerContext locateContext(ClassLoader loader, String configLocation) { 157 String name = loader.toString(); 158 AtomicReference<WeakReference<LoggerContext>> ref = contextMap.get(name); 159 if (ref == null) { 160 LoggerContext ctx = new LoggerContext(name, null, configLocation); 161 AtomicReference<WeakReference<LoggerContext>> r = 162 new AtomicReference<WeakReference<LoggerContext>>(); 163 r.set(new WeakReference<LoggerContext>(ctx)); 164 contextMap.putIfAbsent(loader.toString(), r); 165 ctx = contextMap.get(name).get().get(); 166 return ctx; 167 } else { 168 WeakReference<LoggerContext> r = ref.get(); 169 LoggerContext ctx = r.get(); 170 if (ctx != null) { 171 return ctx; 172 } 173 ctx = new LoggerContext(name, null, configLocation); 174 ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx)); 175 return ctx; 176 } 177 } 178 179 private static void setupCallerCheck() { 180 try { 181 ClassLoader loader = Loader.getClassLoader(); 182 Class clazz = loader.loadClass("sun.reflect.Reflection"); 183 Method[] methods = clazz.getMethods(); 184 for (Method method : methods) { 185 int modifier = method.getModifiers(); 186 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) { 187 getCallerClass = method; 188 break; 189 } 190 } 191 } catch (ClassNotFoundException cnfe) { 192 logger.debug("sun.reflect.Reflection is not installed"); 193 } 194 try { 195 securityManager = new PrivateSecurityManager(); 196 } catch (Exception ex) { 197 ex.printStackTrace(); 198 logger.debug("Unable to install security manager", ex); 199 } 200 } 201 202 private LoggerContext getDefault() { 203 LoggerContext ctx = context.get(); 204 if (ctx != null) { 205 return ctx; 206 } 207 context.compareAndSet(null, new LoggerContext("Default")); 208 return context.get(); 209 } 210 211 /** 212 * SecurityManager that will locate the caller of the Log4j2 API. 213 */ 214 private static class PrivateSecurityManager extends SecurityManager { 215 216 public Class getCaller(String fqcn) { 217 Class[] classes = getClassContext(); 218 boolean next = false; 219 for (Class clazz : classes) { 220 if (clazz.getName().equals(fqcn)) { 221 next = true; 222 continue; 223 } 224 if (next) { 225 return clazz; 226 } 227 } 228 return null; 229 } 230 } 231 232 }