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