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 assigned
37   * to static variables to be released along with the classes that own then. Other ContextSelectors will generally cause
38   * Loggers associated with classes loaded from different ClassLoaders to be co-mingled. This is a problem if, for
39   * example, a web application is undeployed as some of the Loggers being released may be associated with a Class in a
40   * 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> DEFAULT_CONTEXT = new AtomicReference<>();
49  
50      protected static final StatusLogger LOGGER = StatusLogger.getLogger();
51  
52      protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
53              new ConcurrentHashMap<>();
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<>();
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 = createContext(name, configLocation);
146             final AtomicReference<WeakReference<LoggerContext>> r = new AtomicReference<>();
147             r.set(new WeakReference<>(ctx));
148             CONTEXT_MAP.putIfAbsent(name, r);
149             ctx = CONTEXT_MAP.get(name).get().get();
150             return ctx;
151         }
152         final WeakReference<LoggerContext> weakRef = ref.get();
153         LoggerContext ctx = weakRef.get();
154         if (ctx != null) {
155             if (ctx.getConfigLocation() == null && configLocation != null) {
156                 LOGGER.debug("Setting configuration to {}", configLocation);
157                 ctx.setConfigLocation(configLocation);
158             } else if (ctx.getConfigLocation() != null && configLocation != null
159                     && !ctx.getConfigLocation().equals(configLocation)) {
160                 LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation,
161                         ctx.getConfigLocation());
162             }
163             return ctx;
164         }
165         ctx = createContext(name, configLocation);
166         ref.compareAndSet(weakRef, new WeakReference<>(ctx));
167         return ctx;
168     }
169 
170     protected LoggerContext createContext(final String name, final URI configLocation) {
171         return new LoggerContext(name, null, configLocation);
172     }
173 
174     protected String toContextMapKey(final ClassLoader loader) {
175         return Integer.toHexString(System.identityHashCode(loader));
176     }
177 
178     protected LoggerContext getDefault() {
179         final LoggerContext ctx = DEFAULT_CONTEXT.get();
180         if (ctx != null) {
181             return ctx;
182         }
183         DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null));
184         return DEFAULT_CONTEXT.get();
185     }
186 
187     protected String defaultContextName() {
188         return "Default";
189     }
190 }