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