001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core.selector;
018
019 import java.lang.ref.WeakReference;
020 import java.net.URI;
021 import java.util.ArrayList;
022 import java.util.Collection;
023 import java.util.Collections;
024 import java.util.List;
025 import java.util.Map;
026 import java.util.concurrent.ConcurrentHashMap;
027 import java.util.concurrent.ConcurrentMap;
028 import java.util.concurrent.atomic.AtomicReference;
029
030 import org.apache.logging.log4j.core.LoggerContext;
031 import org.apache.logging.log4j.core.impl.ContextAnchor;
032 import org.apache.logging.log4j.status.StatusLogger;
033 import org.apache.logging.log4j.util.ReflectionUtil;
034
035 /**
036 * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers
037 * assigned to static variables to be released along with the classes that own then. Other ContextSelectors
038 * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled.
039 * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be
040 * associated with a Class in a parent ClassLoader, which will generally have negative consequences.
041 *
042 * The main downside to this ContextSelector is that Configuration is more challenging.
043 *
044 * This ContextSelector should not be used with a Servlet Filter such as the Log4jServletFilter.
045 */
046 public class ClassLoaderContextSelector implements ContextSelector {
047
048 private static final AtomicReference<LoggerContext> CONTEXT = new AtomicReference<LoggerContext>();
049
050 protected static final StatusLogger LOGGER = StatusLogger.getLogger();
051
052 protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
053 new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
054
055 @Override
056 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
057 return getContext(fqcn, loader, currentContext, null);
058 }
059
060 @Override
061 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
062 final URI configLocation) {
063 if (currentContext) {
064 final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
065 if (ctx != null) {
066 return ctx;
067 }
068 return getDefault();
069 } else if (loader != null) {
070 return locateContext(loader, configLocation);
071 } else {
072 final Class<?> clazz = ReflectionUtil.getCallerClass(fqcn);
073 if (clazz != null) {
074 return locateContext(clazz.getClassLoader(), configLocation);
075 }
076 final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
077 if (lc != null) {
078 return lc;
079 }
080 return getDefault();
081 }
082 }
083
084 @Override
085 public void removeContext(final LoggerContext context) {
086 for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
087 final LoggerContext ctx = entry.getValue().get().get();
088 if (ctx == context) {
089 CONTEXT_MAP.remove(entry.getKey());
090 }
091 }
092 }
093
094 @Override
095 public List<LoggerContext> getLoggerContexts() {
096 final List<LoggerContext> list = new ArrayList<LoggerContext>();
097 final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
098 for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
099 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 }