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.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.concurrent.ConcurrentMap;
34  import java.util.concurrent.atomic.AtomicReference;
35  
36  /**
37   * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers
38   * assigned to static variables to be released along with the classes that own then. Other ContextSelectors
39   * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled.
40   * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be
41   * associated with a Class in a parent ClassLoader, which will generally have negative consequences.
42   *
43   * The main downside to this ContextSelector is that Configuration is more challenging.
44   *
45   * This ContextSelector should not be used with a Servlet Filter such as the JNDIContextFilter.
46   */
47  public class ClassLoaderContextSelector implements ContextSelector {
48  
49      private static final AtomicReference<LoggerContext> context = new AtomicReference<LoggerContext>();
50  
51      private static PrivateSecurityManager securityManager;
52  
53      private static Method getCallerClass;
54  
55      private static final StatusLogger logger = StatusLogger.getLogger();
56  
57      private static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> contextMap =
58          new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
59  
60      static {
61          setupCallerCheck();
62      }
63  
64      public LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext) {
65          if (currentContext) {
66              LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
67              if (ctx != null) {
68                  return ctx;
69              }
70              return getDefault();
71          } else if (loader != null) {
72              return locateContext(loader, null);
73          } else {
74              if (getCallerClass != null) {
75                  try {
76                      Class clazz = Class.class;
77                      boolean next = false;
78                      for (int index = 2; clazz != null; ++index) {
79                          Object[] params = new Object[] {index};
80                          clazz = (Class) getCallerClass.invoke(null, params);
81                          if (clazz == null) {
82                              break;
83                          }
84                          if (clazz.getName().equals(fqcn)) {
85                              next = true;
86                              continue;
87                          }
88                          if (next) {
89                              break;
90                          }
91                      }
92                      if (clazz != null) {
93                          return locateContext(clazz.getClassLoader(), null);
94                      }
95                  } catch (Exception ex) {
96                      // logger.debug("Unable to determine caller class via Sun Reflection", ex);
97                  }
98              }
99  
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 }