View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.selector;
18  
19  import java.lang.ref.WeakReference;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  import java.net.URI;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.ConcurrentMap;
30  import java.util.concurrent.atomic.AtomicReference;
31  
32  import org.apache.logging.log4j.core.LoggerContext;
33  import org.apache.logging.log4j.core.helpers.Loader;
34  import org.apache.logging.log4j.core.impl.ContextAnchor;
35  import org.apache.logging.log4j.status.StatusLogger;
36  
37  /**
38   * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers
39   * assigned to static variables to be released along with the classes that own then. Other ContextSelectors
40   * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled.
41   * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be
42   * associated with a Class in a parent ClassLoader, which will generally have negative consequences.
43   *
44   * The main downside to this ContextSelector is that Configuration is more challenging.
45   *
46   * This ContextSelector should not be used with a Servlet Filter such as the JNDIContextFilter.
47   */
48  public class ClassLoaderContextSelector implements ContextSelector {
49  
50      private static final AtomicReference<LoggerContext> CONTEXT = new AtomicReference<LoggerContext>();
51  
52      private static PrivateSecurityManager securityManager;
53  
54      private static Method getCallerClass;
55  
56      private static final StatusLogger LOGGER = StatusLogger.getLogger();
57  
58      private static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
59          new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
60  
61      static {
62          setupCallerCheck();
63      }
64  
65      @Override
66      public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
67          return getContext(fqcn, loader, currentContext, null);
68      }
69  
70      @Override
71      public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
72                                      URI configLocation) {
73          if (currentContext) {
74              final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
75              if (ctx != null) {
76                  return ctx;
77              }
78              return getDefault();
79          } else if (loader != null) {
80              return locateContext(loader, configLocation);
81          } else {
82              if (getCallerClass != null) {
83                  try {
84                      Class clazz = Class.class;
85                      boolean next = false;
86                      for (int index = 2; clazz != null; ++index) {
87                          final Object[] params = new Object[] {index};
88                          clazz = (Class) getCallerClass.invoke(null, params);
89                          if (clazz == null) {
90                              break;
91                          }
92                          if (clazz.getName().equals(fqcn)) {
93                              next = true;
94                              continue;
95                          }
96                          if (next) {
97                              break;
98                          }
99                      }
100                     if (clazz != null) {
101                         return locateContext(clazz.getClassLoader(), configLocation);
102                     }
103                 } catch (final Exception ex) {
104                     // logger.debug("Unable to determine caller class via Sun Reflection", ex);
105                 }
106             }
107 
108             if (securityManager != null) {
109                 final Class clazz = securityManager.getCaller(fqcn);
110                 if (clazz != null) {
111                     final ClassLoader ldr = clazz.getClassLoader() != null ? clazz.getClassLoader() :
112                         ClassLoader.getSystemClassLoader();
113                     return locateContext(ldr, configLocation);
114                 }
115             }
116 
117             final Throwable t = new Throwable();
118             boolean next = false;
119             String name = null;
120             for (final StackTraceElement element : t.getStackTrace()) {
121                 if (element.getClassName().equals(fqcn)) {
122                     next = true;
123                     continue;
124                 }
125                 if (next) {
126                     name = element.getClassName();
127                     break;
128                 }
129             }
130             if (name != null) {
131                 try {
132                     return locateContext(Loader.loadClass(name).getClassLoader(), configLocation);
133                 } catch (final ClassNotFoundException ex) {
134                     //System.out.println("Could not load class " + name);
135                 }
136             }
137             final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
138             if (lc != null) {
139                 return lc;
140             }
141             return getDefault();
142         }
143     }
144 
145     @Override
146     public void removeContext(final LoggerContext context) {
147         for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
148             final LoggerContext ctx = entry.getValue().get().get();
149             if (ctx == context) {
150                 CONTEXT_MAP.remove(entry.getKey());
151             }
152         }
153     }
154 
155     @Override
156     public List<LoggerContext> getLoggerContexts() {
157         final List<LoggerContext> list = new ArrayList<LoggerContext>();
158         final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
159         for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
160             final LoggerContext ctx = ref.get().get();
161             if (ctx != null) {
162                 list.add(ctx);
163             }
164         }
165         return Collections.unmodifiableList(list);
166     }
167 
168     private LoggerContext locateContext(final ClassLoader loader, final URI configLocation) {
169         final String name = loader.toString();
170         AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
171         if (ref == null) {
172             if (configLocation == null) {
173                 ClassLoader parent = loader.getParent();
174                 while (parent != null) {
175 
176                     ref = CONTEXT_MAP.get(parent.toString());
177                     if (ref != null) {
178                         final WeakReference<LoggerContext> r = ref.get();
179                         LoggerContext ctx = r.get();
180                         if (ctx != null) {
181                             return ctx;
182                         }
183                     }
184                     parent = parent.getParent();
185                     /*  In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be
186                     configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader.
187                     In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the
188                     ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader
189                     that is configured by the WebAppContextListener.
190 
191                     ClassLoader threadLoader = null;
192                     try {
193                         threadLoader = Thread.currentThread().getContextClassLoader();
194                     } catch (Exception ex) {
195                         // Ignore SecurityException
196                     }
197                     if (threadLoader != null && threadLoader == parent) {
198                         break;
199                     } else {
200                         parent = parent.getParent();
201                     } */
202                 }
203             }
204             LoggerContext ctx = new LoggerContext(name, null, configLocation);
205             final AtomicReference<WeakReference<LoggerContext>> r =
206                 new AtomicReference<WeakReference<LoggerContext>>();
207             r.set(new WeakReference<LoggerContext>(ctx));
208             CONTEXT_MAP.putIfAbsent(loader.toString(), r);
209             ctx = CONTEXT_MAP.get(name).get().get();
210             return ctx;
211         } else {
212             final WeakReference<LoggerContext> r = ref.get();
213             LoggerContext ctx = r.get();
214             if (ctx != null) {
215                 return ctx;
216             }
217             ctx = new LoggerContext(name, null, configLocation);
218             ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx));
219             return ctx;
220         }
221     }
222 
223     private static void setupCallerCheck() {
224         try {
225             final ClassLoader loader = Loader.getClassLoader();
226             final Class clazz = loader.loadClass("sun.reflect.Reflection");
227             final Method[] methods = clazz.getMethods();
228             for (final Method method : methods) {
229                 final int modifier = method.getModifiers();
230                 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) {
231                     getCallerClass = method;
232                     break;
233                 }
234             }
235         } catch (final ClassNotFoundException cnfe) {
236             LOGGER.debug("sun.reflect.Reflection is not installed");
237         }
238         try {
239             securityManager = new PrivateSecurityManager();
240         } catch (final Exception ex) {
241             ex.printStackTrace();
242             LOGGER.debug("Unable to install security manager", ex);
243         }
244     }
245 
246     private LoggerContext getDefault() {
247         final LoggerContext ctx = CONTEXT.get();
248         if (ctx != null) {
249             return ctx;
250         }
251         CONTEXT.compareAndSet(null, new LoggerContext("Default"));
252         return CONTEXT.get();
253     }
254 
255     /**
256      * SecurityManager that will locate the caller of the Log4j 2 API.
257      */
258     private static class PrivateSecurityManager extends SecurityManager {
259 
260         public Class getCaller(final String fqcn) {
261             final Class[] classes = getClassContext();
262             boolean next = false;
263             for (final Class clazz : classes) {
264                 if (clazz.getName().equals(fqcn)) {
265                     next = true;
266                     continue;
267                 }
268                 if (next) {
269                     return clazz;
270                 }
271             }
272             return null;
273         }
274     }
275 
276 }