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