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>>> CONTEXT_MAP =
58          new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
59  
60      static {
61          setupCallerCheck();
62      }
63  
64      public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
65          if (currentContext) {
66              final 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                          final 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 (final Exception ex) {
96                      // logger.debug("Unable to determine caller class via Sun Reflection", ex);
97                  }
98              }
99  
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 }