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