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