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