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