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.Reference;
20  import java.lang.ref.WeakReference;
21  import java.net.URI;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentMap;
29  import java.util.concurrent.TimeUnit;
30  import java.util.concurrent.atomic.AtomicReference;
31  
32  import org.apache.logging.log4j.core.LoggerContext;
33  import org.apache.logging.log4j.core.impl.ContextAnchor;
34  import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
35  import org.apache.logging.log4j.status.StatusLogger;
36  import org.apache.logging.log4j.util.StackLocatorUtil;
37  
38  /**
39   * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers assigned
40   * to static variables to be released along with the classes that own then. Other ContextSelectors will generally cause
41   * Loggers associated with classes loaded from different ClassLoaders to be co-mingled. This is a problem if, for
42   * example, a web application is undeployed as some of the Loggers being released may be associated with a Class in a
43   * parent ClassLoader, which will generally have negative consequences.
44   *
45   * The main downside to this ContextSelector is that Configuration is more challenging.
46   *
47   * This ContextSelector should not be used with a Servlet Filter such as the Log4jServletFilter.
48   */
49  public class ClassLoaderContextSelector implements ContextSelector, LoggerContextShutdownAware {
50  
51      private static final AtomicReference<LoggerContext> DEFAULT_CONTEXT = new AtomicReference<>();
52  
53      protected static final StatusLogger LOGGER = StatusLogger.getLogger();
54  
55      protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
56              new ConcurrentHashMap<>();
57  
58      @Override
59      public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
60                           final boolean allContexts) {
61          LoggerContext ctx = null;
62          if (currentContext) {
63              ctx = ContextAnchor.THREAD_CONTEXT.get();
64          } else if (loader != null) {
65              ctx = findContext(loader);
66          } else {
67              final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
68              if (clazz != null) {
69                  ctx = findContext(clazz.getClassLoader());
70              }
71              if (ctx == null) {
72                  ctx = ContextAnchor.THREAD_CONTEXT.get();
73              }
74          }
75          if (ctx != null) {
76              ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
77          }
78      }
79  
80      @Override
81      public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext loggerContext) {
82          if (loggerContext instanceof LoggerContext) {
83              removeContext((LoggerContext) loggerContext);
84          }
85      }
86  
87      @Override
88      public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
89          LoggerContext ctx;
90          if (currentContext) {
91              ctx = ContextAnchor.THREAD_CONTEXT.get();
92          } else if (loader != null) {
93              ctx = findContext(loader);
94          } else {
95              final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
96              if (clazz != null) {
97                  ctx = findContext(clazz.getClassLoader());
98              } else {
99                  ctx = ContextAnchor.THREAD_CONTEXT.get();
100             }
101         }
102         return ctx != null && ctx.isStarted();
103     }
104 
105     private LoggerContext findContext(ClassLoader loaderOrNull) {
106         final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
107         final String name = toContextMapKey(loader);
108         AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
109         if (ref != null) {
110             final WeakReference<LoggerContext> weakRef = ref.get();
111             return weakRef.get();
112         }
113         return null;
114     }
115 
116     @Override
117     public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
118         return getContext(fqcn, loader, currentContext, null);
119     }
120 
121     @Override
122     public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
123             final URI configLocation) {
124         if (currentContext) {
125             final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
126             if (ctx != null) {
127                 return ctx;
128             }
129             return getDefault();
130         } else if (loader != null) {
131             return locateContext(loader, configLocation);
132         } else {
133             final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
134             if (clazz != null) {
135                 return locateContext(clazz.getClassLoader(), configLocation);
136             }
137             final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
138             if (lc != null) {
139                 return lc;
140             }
141             return getDefault();
142         }
143     }
144 
145     @Override
146     public void removeContext(final LoggerContext context) {
147         for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
148             final LoggerContext ctx = entry.getValue().get().get();
149             if (ctx == context) {
150                 CONTEXT_MAP.remove(entry.getKey());
151             }
152         }
153     }
154 
155     @Override
156     public List<LoggerContext> getLoggerContexts() {
157         final List<LoggerContext> list = new ArrayList<>();
158         final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
159         for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
160             final LoggerContext ctx = ref.get().get();
161             if (ctx != null) {
162                 list.add(ctx);
163             }
164         }
165         return Collections.unmodifiableList(list);
166     }
167 
168     private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) {
169         // LOG4J2-477: class loader may be null
170         final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
171         final String name = toContextMapKey(loader);
172         AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
173         if (ref == null) {
174             if (configLocation == null) {
175                 ClassLoader parent = loader.getParent();
176                 while (parent != null) {
177 
178                     ref = CONTEXT_MAP.get(toContextMapKey(parent));
179                     if (ref != null) {
180                         final WeakReference<LoggerContext> r = ref.get();
181                         final LoggerContext ctx = r.get();
182                         if (ctx != null) {
183                             return ctx;
184                         }
185                     }
186                     parent = parent.getParent();
187                     /*  In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be
188                     configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader.
189                     In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the
190                     ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader
191                     that is configured by the WebAppContextListener.
192 
193                     ClassLoader threadLoader = null;
194                     try {
195                         threadLoader = Thread.currentThread().getContextClassLoader();
196                     } catch (Exception ex) {
197                         // Ignore SecurityException
198                     }
199                     if (threadLoader != null && threadLoader == parent) {
200                         break;
201                     } else {
202                         parent = parent.getParent();
203                     } */
204                 }
205             }
206             LoggerContext ctx = createContext(name, configLocation);
207             LoggerContext newContext = CONTEXT_MAP.computeIfAbsent(name,
208                     k -> new AtomicReference<>(new WeakReference<>(ctx))).get().get();
209             if (newContext == ctx) {
210                 ctx.addShutdownListener(this);
211             }
212             return newContext;
213         }
214         final WeakReference<LoggerContext> weakRef = ref.get();
215         LoggerContext ctx = weakRef.get();
216         if (ctx != null) {
217             if (ctx.getConfigLocation() == null && configLocation != null) {
218                 LOGGER.debug("Setting configuration to {}", configLocation);
219                 ctx.setConfigLocation(configLocation);
220             } else if (ctx.getConfigLocation() != null && configLocation != null
221                     && !ctx.getConfigLocation().equals(configLocation)) {
222                 LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation,
223                         ctx.getConfigLocation());
224             }
225             return ctx;
226         }
227         ctx = createContext(name, configLocation);
228         ref.compareAndSet(weakRef, new WeakReference<>(ctx));
229         return ctx;
230     }
231 
232     protected LoggerContext createContext(final String name, final URI configLocation) {
233         return new LoggerContext(name, null, configLocation);
234     }
235 
236     protected String toContextMapKey(final ClassLoader loader) {
237         return Integer.toHexString(System.identityHashCode(loader));
238     }
239 
240     protected LoggerContext getDefault() {
241         final LoggerContext ctx = DEFAULT_CONTEXT.get();
242         if (ctx != null) {
243             return ctx;
244         }
245         DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null));
246         return DEFAULT_CONTEXT.get();
247     }
248 
249     protected String defaultContextName() {
250         return "Default";
251     }
252 }