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.web;
18  
19  import java.net.URI;
20  import java.net.URL;
21  import java.text.SimpleDateFormat;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Date;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.TimeUnit;
29  
30  import javax.servlet.ServletContext;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.core.AbstractLifeCycle;
34  import org.apache.logging.log4j.core.LoggerContext;
35  import org.apache.logging.log4j.core.async.AsyncLoggerContext;
36  import org.apache.logging.log4j.core.config.Configurator;
37  import org.apache.logging.log4j.core.impl.ContextAnchor;
38  import org.apache.logging.log4j.core.impl.Log4jContextFactory;
39  import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
40  import org.apache.logging.log4j.core.lookup.Interpolator;
41  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
42  import org.apache.logging.log4j.core.selector.ContextSelector;
43  import org.apache.logging.log4j.core.selector.NamedContextSelector;
44  import org.apache.logging.log4j.core.util.Loader;
45  import org.apache.logging.log4j.core.util.NetUtils;
46  import org.apache.logging.log4j.core.util.SetUtils;
47  import org.apache.logging.log4j.spi.LoggerContextFactory;
48  import org.apache.logging.log4j.util.LoaderUtil;
49  
50  /**
51   * This class initializes and deinitializes Log4j no matter how the initialization occurs.
52   */
53  final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWebLifeCycle {
54  
55      private static final String WEB_INF = "/WEB-INF/";
56  
57      static {
58          if (Loader.isClassAvailable("org.apache.logging.log4j.core.web.JNDIContextFilter")) {
59              throw new IllegalStateException("You are using Log4j 2 in a web application with the old, extinct "
60                      + "log4j-web artifact. This is not supported and could cause serious runtime problems. Please"
61                      + "remove the log4j-web JAR file from your application.");
62          }
63      }
64  
65      private final Map<String, String> map = new ConcurrentHashMap<>();
66      private final StrSubstitutor substitutor = new ConfigurationStrSubstitutor(new Interpolator(map));
67      private final ServletContext servletContext;
68  
69      private String name;
70      private NamedContextSelector namedContextSelector;
71      private LoggerContext loggerContext;
72  
73      private Log4jWebInitializerImpl(final ServletContext servletContext) {
74          this.servletContext = servletContext;
75          this.map.put("hostName", NetUtils.getLocalHostname());
76      }
77  
78      /**
79       * Initializes the Log4jWebLifeCycle attribute of a ServletContext. Those who wish to obtain this object should use
80       * the {@link org.apache.logging.log4j.web.WebLoggerContextUtils#getWebLifeCycle(javax.servlet.ServletContext)}
81       * method instead.
82       *
83       * @param servletContext
84       *        the ServletContext to initialize
85       * @return a new Log4jWebLifeCycle
86       * @since 2.0.1
87       */
88      protected static Log4jWebInitializerImpl initialize(final ServletContext servletContext) {
89          final Log4jWebInitializerImpl initializer = new Log4jWebInitializerImpl(servletContext);
90          servletContext.setAttribute(SUPPORT_ATTRIBUTE, initializer);
91          return initializer;
92      }
93  
94      @Override
95      public synchronized void start() {
96          if (this.isStopped() || this.isStopping()) {
97              throw new IllegalStateException("Cannot start this Log4jWebInitializerImpl after it was stopped.");
98          }
99  
100         // only do this once
101         if (this.isInitialized()) {
102             super.setStarting();
103 
104             this.name = this.substitutor.replace(this.servletContext.getInitParameter(LOG4J_CONTEXT_NAME));
105             final String location = this.substitutor.replace(this.servletContext
106                     .getInitParameter(LOG4J_CONFIG_LOCATION));
107             final boolean isJndi = "true".equalsIgnoreCase(this.servletContext
108                     .getInitParameter(IS_LOG4J_CONTEXT_SELECTOR_NAMED));
109 
110             if (isJndi) {
111                 this.initializeJndi(location);
112             } else {
113                 this.initializeNonJndi(location);
114             }
115             if (this.loggerContext instanceof AsyncLoggerContext) {
116                 ((AsyncLoggerContext) this.loggerContext).setUseThreadLocals(false);
117             }
118 
119             this.servletContext.setAttribute(CONTEXT_ATTRIBUTE, this.loggerContext);
120             super.setStarted();
121         }
122     }
123 
124     private void initializeJndi(final String location) {
125         final URI configLocation = getConfigURI(location);
126 
127         if (this.name == null) {
128             throw new IllegalStateException("A log4jContextName context parameter is required");
129         }
130 
131         LoggerContext context;
132         final LoggerContextFactory factory = LogManager.getFactory();
133         if (factory instanceof Log4jContextFactory) {
134             final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
135             if (selector instanceof NamedContextSelector) {
136                 this.namedContextSelector = (NamedContextSelector) selector;
137                 context = this.namedContextSelector.locateContext(this.name, this.servletContext, configLocation);
138                 ContextAnchor.THREAD_CONTEXT.set(context);
139                 if (context.isInitialized()) {
140                     context.start();
141                 }
142                 ContextAnchor.THREAD_CONTEXT.remove();
143             } else {
144                 LOGGER.warn("Potential problem: Selector is not an instance of NamedContextSelector.");
145                 return;
146             }
147         } else {
148             LOGGER.warn("Potential problem: LoggerContextFactory is not an instance of Log4jContextFactory.");
149             return;
150         }
151         this.loggerContext = context;
152         LOGGER.debug("Created logger context for [{}] using [{}].", this.name, context.getClass().getClassLoader());
153     }
154 
155     private void initializeNonJndi(final String location) {
156         if (this.name == null) {
157             this.name = this.servletContext.getServletContextName();
158             LOGGER.debug("Using the servlet context name \"{}\".", this.name);
159         }
160         if (this.name == null) {
161             this.name = this.servletContext.getContextPath();
162             LOGGER.debug("Using the servlet context context-path \"{}\".", this.name);
163         }
164         if (this.name == null && location == null) {
165             LOGGER.error("No Log4j context configuration provided. This is very unusual.");
166             this.name = new SimpleDateFormat("yyyyMMdd_HHmmss.SSS").format(new Date());
167         }
168         if (location != null && location.contains(",")) {
169             final List<URI> uris = getConfigURIs(location);
170             this.loggerContext = Configurator.initialize(this.name, this.getClassLoader(), uris, this.servletContext);
171             return;
172         }
173 
174         final URI uri = getConfigURI(location);
175         this.loggerContext = Configurator.initialize(this.name, this.getClassLoader(), uri, this.servletContext);
176     }
177 
178     private List<URI> getConfigURIs(final String location) {
179         final String[] parts = location.split(",");
180         final List<URI> uris = new ArrayList<>(parts.length);
181         for (final String part : parts) {
182             final URI uri = getConfigURI(part);
183             if (uri != null) {
184                 uris.add(uri);
185             }
186         }
187         return uris;
188     }
189 
190     private URI getConfigURI(final String location) {
191         try {
192             String configLocation = location;
193             if (configLocation == null) {
194                 final String[] paths = SetUtils.prefixSet(servletContext.getResourcePaths(WEB_INF), WEB_INF + "log4j2");
195                 LOGGER.debug("getConfigURI found resource paths {} in servletContext at [{}]", Arrays.toString(paths), WEB_INF);
196                 if (paths.length == 1) {
197                     configLocation = paths[0];
198                 } else if (paths.length > 1) {
199                     final String prefix = WEB_INF + "log4j2-" + this.name + ".";
200                     boolean found = false;
201                     for (final String str : paths) {
202                         if (str.startsWith(prefix)) {
203                             configLocation = str;
204                             found = true;
205                             break;
206                         }
207                     }
208                     if (!found) {
209                         configLocation = paths[0];
210                     }
211                 }
212             }
213             if (configLocation != null) {
214                 final URL url = servletContext.getResource(configLocation);
215                 if (url != null) {
216                     final URI uri = url.toURI();
217                     LOGGER.debug("getConfigURI found resource [{}] in servletContext at [{}]", uri, configLocation);
218                     return uri;
219                 }
220             }
221         } catch (final Exception ex) {
222             // Just try passing the location.
223         }
224         if (location != null) {
225             try {
226                 final URI correctedFilePathUri = NetUtils.toURI(location);
227                 LOGGER.debug("getConfigURI found [{}] in servletContext at [{}]", correctedFilePathUri, location);
228                 return correctedFilePathUri;
229             } catch (final Exception e) {
230                 LOGGER.error("Unable to convert configuration location [{}] to a URI", location, e);
231             }
232         }
233         return null;
234     }
235 
236     @Override
237     public synchronized boolean stop(final long timeout, final TimeUnit timeUnit) {
238         if (!this.isStarted() && !this.isStopped()) {
239             throw new IllegalStateException("Cannot stop this Log4jWebInitializer because it has not started.");
240         }
241 
242         // only do this once
243         if (this.isStarted()) {
244             this.setStopping();
245             if (this.loggerContext != null) {
246                 LOGGER.debug("Removing LoggerContext for [{}].", this.name);
247                 this.servletContext.removeAttribute(CONTEXT_ATTRIBUTE);
248                 if (this.namedContextSelector != null) {
249                     this.namedContextSelector.removeContext(this.name);
250                 }
251                 this.loggerContext.stop(timeout, timeUnit);
252                 this.loggerContext.setExternalContext(null);
253                 this.loggerContext = null;
254             }
255             this.setStopped();
256         }
257         return super.stop(timeout, timeUnit);
258     }
259 
260     @Override
261     public void setLoggerContext() {
262         if (this.loggerContext != null) {
263             ContextAnchor.THREAD_CONTEXT.set(this.loggerContext);
264         }
265     }
266 
267     @Override
268     public void clearLoggerContext() {
269         ContextAnchor.THREAD_CONTEXT.remove();
270     }
271 
272     @Override
273     public void wrapExecution(final Runnable runnable) {
274         this.setLoggerContext();
275 
276         try {
277             runnable.run();
278         } finally {
279             this.clearLoggerContext();
280         }
281     }
282 
283     private ClassLoader getClassLoader() {
284         try {
285             // if container is Servlet 3.0, use its getClassLoader method
286             // this may look odd, but the call below will throw NoSuchMethodError if user is on Servlet 2.5
287             // we compile against 3.0 to support Log4jServletContainerInitializer, but we don't require 3.0
288             return this.servletContext.getClassLoader();
289         } catch (final Throwable ignore) {
290             // LOG4J2-248: use TCCL if possible
291             return LoaderUtil.getThreadContextClassLoader();
292         }
293     }
294 
295 }