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