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 org.apache.logging.log4j.core.LoggerContext;
20  import org.apache.logging.log4j.core.helpers.Loader;
21  import org.apache.logging.log4j.core.impl.ContextAnchor;
22  import org.apache.logging.log4j.status.StatusLogger;
23  
24  import java.lang.ref.WeakReference;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.Modifier;
27  import java.net.URI;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.ConcurrentMap;
35  import java.util.concurrent.atomic.AtomicReference;
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      public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
66          return getContext(fqcn, loader, currentContext, null);
67      }
68  
69      public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
70                                      URI configLocation) {
71          if (currentContext) {
72              final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
73              if (ctx != null) {
74                  return ctx;
75              }
76              return getDefault();
77          } else if (loader != null) {
78              return locateContext(loader, configLocation);
79          } else {
80              if (getCallerClass != null) {
81                  try {
82                      Class clazz = Class.class;
83                      boolean next = false;
84                      for (int index = 2; clazz != null; ++index) {
85                          final Object[] params = new Object[] {index};
86                          clazz = (Class) getCallerClass.invoke(null, params);
87                          if (clazz == null) {
88                              break;
89                          }
90                          if (clazz.getName().equals(fqcn)) {
91                              next = true;
92                              continue;
93                          }
94                          if (next) {
95                              break;
96                          }
97                      }
98                      if (clazz != null) {
99                          return locateContext(clazz.getClassLoader(), configLocation);
100                     }
101                 } catch (final Exception ex) {
102                     // logger.debug("Unable to determine caller class via Sun Reflection", ex);
103                 }
104             }
105 
106             if (securityManager != null) {
107                 final Class clazz = securityManager.getCaller(fqcn);
108                 if (clazz != null) {
109                     final ClassLoader ldr = clazz.getClassLoader() != null ? clazz.getClassLoader() :
110                         ClassLoader.getSystemClassLoader();
111                     return locateContext(ldr, configLocation);
112                 }
113             }
114 
115             final Throwable t = new Throwable();
116             boolean next = false;
117             String name = null;
118             for (final StackTraceElement element : t.getStackTrace()) {
119                 if (element.getClassName().equals(fqcn)) {
120                     next = true;
121                     continue;
122                 }
123                 if (next) {
124                     name = element.getClassName();
125                     break;
126                 }
127             }
128             if (name != null) {
129                 try {
130                     return locateContext(Loader.loadClass(name).getClassLoader(), configLocation);
131                 } catch (final ClassNotFoundException ex) {
132                     //System.out.println("Could not load class " + name);
133                 }
134             }
135             final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
136             if (lc != null) {
137                 return lc;
138             }
139             return getDefault();
140         }
141     }
142 
143     public void removeContext(final LoggerContext context) {
144         for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
145             final LoggerContext ctx = entry.getValue().get().get();
146             if (ctx == context) {
147                 CONTEXT_MAP.remove(entry.getKey());
148             }
149         }
150     }
151 
152     public List<LoggerContext> getLoggerContexts() {
153         final List<LoggerContext> list = new ArrayList<LoggerContext>();
154         final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
155         for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
156             final LoggerContext ctx = ref.get().get();
157             if (ctx != null) {
158                 list.add(ctx);
159             }
160         }
161         return Collections.unmodifiableList(list);
162     }
163 
164     private LoggerContext locateContext(final ClassLoader loader, final URI configLocation) {
165         final String name = loader.toString();
166         AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
167         if (ref == null) {
168             if (configLocation == null) {
169                 ClassLoader parent = loader.getParent();
170                 while (parent != null) {
171 
172                     ref = CONTEXT_MAP.get(parent.toString());
173                     if (ref != null) {
174                         final WeakReference<LoggerContext> r = ref.get();
175                         LoggerContext ctx = r.get();
176                         if (ctx != null) {
177                             return ctx;
178                         }
179                     }
180                     parent = parent.getParent();
181                     /*  In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be
182                     configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader.
183                     In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the
184                     ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader
185                     that is configured by the WebAppContextListener.
186 
187                     ClassLoader threadLoader = null;
188                     try {
189                         threadLoader = Thread.currentThread().getContextClassLoader();
190                     } catch (Exception ex) {
191                         // Ignore SecurityException
192                     }
193                     if (threadLoader != null && threadLoader == parent) {
194                         break;
195                     } else {
196                         parent = parent.getParent();
197                     } */
198                 }
199             }
200             LoggerContext ctx = new LoggerContext(name, null, configLocation);
201             final AtomicReference<WeakReference<LoggerContext>> r =
202                 new AtomicReference<WeakReference<LoggerContext>>();
203             r.set(new WeakReference<LoggerContext>(ctx));
204             CONTEXT_MAP.putIfAbsent(loader.toString(), r);
205             ctx = CONTEXT_MAP.get(name).get().get();
206             return ctx;
207         } else {
208             final WeakReference<LoggerContext> r = ref.get();
209             LoggerContext ctx = r.get();
210             if (ctx != null) {
211                 return ctx;
212             }
213             ctx = new LoggerContext(name, null, configLocation);
214             ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx));
215             return ctx;
216         }
217     }
218 
219     private static void setupCallerCheck() {
220         try {
221             final ClassLoader loader = Loader.getClassLoader();
222             final Class clazz = loader.loadClass("sun.reflect.Reflection");
223             final Method[] methods = clazz.getMethods();
224             for (final Method method : methods) {
225                 final int modifier = method.getModifiers();
226                 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) {
227                     getCallerClass = method;
228                     break;
229                 }
230             }
231         } catch (final ClassNotFoundException cnfe) {
232             LOGGER.debug("sun.reflect.Reflection is not installed");
233         }
234         try {
235             securityManager = new PrivateSecurityManager();
236         } catch (final Exception ex) {
237             ex.printStackTrace();
238             LOGGER.debug("Unable to install security manager", ex);
239         }
240     }
241 
242     private LoggerContext getDefault() {
243         final LoggerContext ctx = CONTEXT.get();
244         if (ctx != null) {
245             return ctx;
246         }
247         CONTEXT.compareAndSet(null, new LoggerContext("Default"));
248         return CONTEXT.get();
249     }
250 
251     /**
252      * SecurityManager that will locate the caller of the Log4j2 API.
253      */
254     private static class PrivateSecurityManager extends SecurityManager {
255 
256         public Class getCaller(final String fqcn) {
257             final Class[] classes = getClassContext();
258             boolean next = false;
259             for (final Class clazz : classes) {
260                 if (clazz.getName().equals(fqcn)) {
261                     next = true;
262                     continue;
263                 }
264                 if (next) {
265                     return clazz;
266                 }
267             }
268             return null;
269         }
270     }
271 
272 }