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