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.status.StatusLogger;
33  import org.apache.logging.log4j.util.ReflectionUtil;
34  
35  /**
36   * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers
37   * assigned to static variables to be released along with the classes that own then. Other ContextSelectors
38   * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled.
39   * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be
40   * associated with a Class in a parent ClassLoader, which will generally have negative consequences.
41   *
42   * The main downside to this ContextSelector is that Configuration is more challenging.
43   *
44   * This ContextSelector should not be used with a Servlet Filter such as the Log4jServletFilter.
45   */
46  public class ClassLoaderContextSelector implements ContextSelector {
47  
48      private static final AtomicReference<LoggerContext> CONTEXT = new AtomicReference<LoggerContext>();
49  
50      protected static final StatusLogger LOGGER = StatusLogger.getLogger();
51  
52      protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
53          new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
54  
55      @Override
56      public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
57          return getContext(fqcn, loader, currentContext, null);
58      }
59  
60      @Override
61      public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
62                                      final URI configLocation) {
63          if (currentContext) {
64              final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
65              if (ctx != null) {
66                  return ctx;
67              }
68              return getDefault();
69          } else if (loader != null) {
70              return locateContext(loader, configLocation);
71          } else {
72              final Class<?> clazz = ReflectionUtil.getCallerClass(fqcn);
73              if (clazz != null) {
74                  return locateContext(clazz.getClassLoader(), configLocation);
75              }
76              final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
77              if (lc != null) {
78                  return lc;
79              }
80              return getDefault();
81          }
82      }
83  
84      @Override
85      public void removeContext(final LoggerContext context) {
86          for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
87              final LoggerContext ctx = entry.getValue().get().get();
88              if (ctx == context) {
89                  CONTEXT_MAP.remove(entry.getKey());
90              }
91          }
92      }
93  
94      @Override
95      public List<LoggerContext> getLoggerContexts() {
96          final List<LoggerContext> list = new ArrayList<LoggerContext>();
97          final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
98          for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
99              final LoggerContext ctx = ref.get().get();
100             if (ctx != null) {
101                 list.add(ctx);
102             }
103         }
104         return Collections.unmodifiableList(list);
105     }
106 
107     private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) {
108         // LOG4J2-477: class loader may be null
109         final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
110         final String name = toContextMapKey(loader);
111         AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
112         if (ref == null) {
113             if (configLocation == null) {
114                 ClassLoader parent = loader.getParent();
115                 while (parent != null) {
116 
117                     ref = CONTEXT_MAP.get(toContextMapKey(parent));
118                     if (ref != null) {
119                         final WeakReference<LoggerContext> r = ref.get();
120                         final LoggerContext ctx = r.get();
121                         if (ctx != null) {
122                             return ctx;
123                         }
124                     }
125                     parent = parent.getParent();
126                     /*  In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be
127                     configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader.
128                     In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the
129                     ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader
130                     that is configured by the WebAppContextListener.
131 
132                     ClassLoader threadLoader = null;
133                     try {
134                         threadLoader = Thread.currentThread().getContextClassLoader();
135                     } catch (Exception ex) {
136                         // Ignore SecurityException
137                     }
138                     if (threadLoader != null && threadLoader == parent) {
139                         break;
140                     } else {
141                         parent = parent.getParent();
142                     } */
143                 }
144             }
145             LoggerContext ctx = new LoggerContext(name, null, configLocation);
146             final AtomicReference<WeakReference<LoggerContext>> r =
147                 new AtomicReference<WeakReference<LoggerContext>>();
148             r.set(new WeakReference<LoggerContext>(ctx));
149             CONTEXT_MAP.putIfAbsent(name, r);
150             ctx = CONTEXT_MAP.get(name).get().get();
151             return ctx;
152         }
153         final WeakReference<LoggerContext> weakRef = ref.get();
154         LoggerContext ctx = weakRef.get();
155         if (ctx != null) {
156             if (ctx.getConfigLocation() == null && configLocation != null) {
157                 LOGGER.debug("Setting configuration to {}", configLocation);
158                 ctx.setConfigLocation(configLocation);
159             } else if (ctx.getConfigLocation() != null && configLocation != null &&
160                 !ctx.getConfigLocation().equals(configLocation)) {
161                 LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation,
162                     ctx.getConfigLocation());
163             }
164             return ctx;
165         }
166         ctx = new LoggerContext(name, null, configLocation);
167         ref.compareAndSet(weakRef, new WeakReference<LoggerContext>(ctx));
168         return ctx;
169     }
170 
171     private String toContextMapKey(final ClassLoader loader) {
172         return String.valueOf(System.identityHashCode(loader));
173     }
174 
175     protected LoggerContext getDefault() {
176         final LoggerContext ctx = CONTEXT.get();
177         if (ctx != null) {
178             return ctx;
179         }
180         CONTEXT.compareAndSet(null, new LoggerContext("Default"));
181         return CONTEXT.get();
182     }
183 
184 }