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.core.web;
18  
19  import java.net.URI;
20  import java.util.Map;
21  import java.util.concurrent.ConcurrentHashMap;
22  import javax.servlet.ServletContext;
23  import javax.servlet.UnavailableException;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.core.LoggerContext;
27  import org.apache.logging.log4j.core.config.Configurator;
28  import org.apache.logging.log4j.core.helpers.FileUtils;
29  import org.apache.logging.log4j.core.helpers.NetUtils;
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.Interpolator;
33  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
34  import org.apache.logging.log4j.core.selector.ContextSelector;
35  import org.apache.logging.log4j.core.selector.NamedContextSelector;
36  import org.apache.logging.log4j.spi.LoggerContextFactory;
37  
38  /**
39   * This class initializes and deinitializes Log4j no matter how the initialization occurs.
40   */
41  final class Log4jWebInitializerImpl implements Log4jWebInitializer {
42      private static final Object MUTEX = new Object();
43  
44      static {
45          try {
46              Class.forName("org.apache.logging.log4j.core.web.JNDIContextFilter");
47              throw new IllegalStateException("You are using Log4j 2 in a web application with the old, extinct " +
48                      "log4j-web artifact. This is not supported and could cause serious runtime problems. Please" +
49                      "remove the log4j-web JAR file from your application.");
50          } catch (final ClassNotFoundException ignore) {
51              /* Good. They don't have the old log4j-web artifact loaded. */
52          }
53      }
54  
55      private final Map<String, String> map = new ConcurrentHashMap<String, String>();
56      private final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator(map));
57      private final ServletContext servletContext;
58  
59      private String name;
60      private NamedContextSelector selector;
61      private LoggerContext loggerContext;
62  
63      private boolean initialized = false;
64      private boolean deinitialized = false;
65  
66      private Log4jWebInitializerImpl(final ServletContext servletContext) {
67          this.servletContext = servletContext;
68          map.put("hostName", NetUtils.getLocalHostname());
69      }
70  
71      @Override
72      public synchronized void initialize() throws UnavailableException {
73          if (this.deinitialized) {
74              throw new IllegalStateException("Cannot initialize Log4jWebInitializer after it was destroyed.");
75          }
76  
77          // only do this once
78          if (!this.initialized) {
79              this.initialized = true;
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          }
95      }
96  
97      private void initializeJndi(final String location) throws UnavailableException {
98          URI configLocation = null;
99          if (location != null) {
100             try {
101                 configLocation = FileUtils.getCorrectedFilePathUri(location);
102             } catch (final Exception e) {
103                 this.servletContext.log("Unable to convert configuration location [" + location + "] to a URI!", e);
104             }
105         }
106 
107         if (this.name == null) {
108             throw new UnavailableException("A log4jContextName context parameter is required");
109         }
110 
111         LoggerContext loggerContext;
112         final LoggerContextFactory factory = LogManager.getFactory();
113         if (factory instanceof Log4jContextFactory) {
114             final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
115             if (selector instanceof NamedContextSelector) {
116                 this.selector = (NamedContextSelector) selector;
117                 loggerContext = this.selector.locateContext(this.name, this.servletContext, configLocation);
118                 ContextAnchor.THREAD_CONTEXT.set(loggerContext);
119                 if (loggerContext.getStatus() == LoggerContext.Status.INITIALIZED) {
120                     loggerContext.start();
121                 }
122                 ContextAnchor.THREAD_CONTEXT.remove();
123             } else {
124                 this.servletContext.log("Potential problem: Selector is not an instance of NamedContextSelector.");
125                 return;
126             }
127         } else {
128             this.servletContext.log("Potential problem: Factory is not an instance of Log4jContextFactory.");
129             return;
130         }
131         this.loggerContext = loggerContext;
132         this.servletContext.log("Created logger context for [" + this.name + "] using [" +
133                 loggerContext.getClass().getClassLoader() + "].");
134     }
135 
136     private void initializeNonJndi(final String location) {
137         if (this.name == null) {
138             this.name = this.servletContext.getServletContextName();
139         }
140 
141         if (this.name == null && location == null) {
142             this.servletContext.log("No Log4j context configuration provided. This is very unusual.");
143             return;
144         }
145 
146         this.loggerContext = Configurator.initialize(this.name, this.getClassLoader(), location, this.servletContext);
147     }
148 
149     @Override
150     public synchronized void deinitialize() {
151         if (!this.initialized) {
152             throw new IllegalStateException("Cannot deinitialize Log4jWebInitializer because it has not initialized.");
153         }
154 
155         // only do this once
156         if (!this.deinitialized) {
157             this.deinitialized = true;
158 
159             if (this.loggerContext != null) {
160                 this.servletContext.log("Removing LoggerContext for [" + this.name + "].");
161                 this.servletContext.removeAttribute(CONTEXT_ATTRIBUTE);
162                 if (this.selector != null) {
163                     this.selector.removeContext(this.name);
164                 }
165                 this.loggerContext.stop();
166                 this.loggerContext.setExternalContext(null);
167                 this.loggerContext = null;
168             }
169         }
170     }
171 
172     @Override
173     public void setLoggerContext() {
174         if (this.loggerContext != null) {
175             ContextAnchor.THREAD_CONTEXT.set(this.loggerContext);
176         }
177     }
178 
179     @Override
180     public void clearLoggerContext() {
181         ContextAnchor.THREAD_CONTEXT.remove();
182     }
183 
184     @Override
185     public void wrapExecution(Runnable runnable) {
186         this.setLoggerContext();
187 
188         try {
189             runnable.run();
190         } finally {
191             this.clearLoggerContext();
192         }
193     }
194 
195     private ClassLoader getClassLoader() {
196         try {
197             // if container is Servlet 3.0, use its getClassLoader method
198             // this may look odd, but the call below will throw NoSuchMethodError if user is on Servlet 2.5
199             // we compile against 3.0 to support Log4jServletContainerInitializer, but we don't require 3.0
200             return this.servletContext.getClassLoader();
201         } catch (final Throwable ignore) {
202             // otherwise, use this class's class loader
203             return Log4jWebInitializerImpl.class.getClassLoader();
204         }
205     }
206 
207     /**
208      * Get the current initializer from the {@link ServletContext}. If the initializer does not exist, create a new one
209      * and add it to the {@link ServletContext}, then return that.
210      *
211      * @param servletContext The {@link ServletContext} for this web application
212      * @return the initializer, never {@code null}.
213      */
214     static Log4jWebInitializer getLog4jWebInitializer(final ServletContext servletContext) {
215         synchronized (MUTEX) {
216             Log4jWebInitializer initializer = (Log4jWebInitializer) servletContext.getAttribute(SUPPORT_ATTRIBUTE);
217             if (initializer == null) {
218                 initializer = new Log4jWebInitializerImpl(servletContext);
219                 servletContext.setAttribute(SUPPORT_ATTRIBUTE, initializer);
220             }
221             return initializer;
222         }
223     }
224 }