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