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