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.Logger;
    27  import org.apache.logging.log4j.core.AbstractLifeCycle;
    28  import org.apache.logging.log4j.core.LoggerContext;
    29  import org.apache.logging.log4j.core.config.Configurator;
    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.core.util.FileUtils;
    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  import org.apache.logging.log4j.status.StatusLogger;
    42
    43  /**
    44   * This class initializes and deinitializes Log4j no matter how the initialization occurs.
    45   */
    46  final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWebLifeCycle {
    47
    48      private static final Logger LOGGER = StatusLogger.getLogger();
    49
    50      private static final long serialVersionUID = 1L;
    51
    52      static {
    53          if (Loader.isClassAvailable("org.apache.logging.log4j.core.web.JNDIContextFilter")) {
    54              throw new IllegalStateException("You are using Log4j 2 in a web application with the old, extinct " +
    55                      "log4j-web artifact. This is not supported and could cause serious runtime problems. Please" +
    56                      "remove the log4j-web JAR file from your application.");
    57          }
    58      }
    59
    60      private final Map<String, String> map = new ConcurrentHashMap<String, String>();
    61      private final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator(map));
    62      private final ServletContext servletContext;
    63
    64      private String name;
    65      private NamedContextSelector namedContextSelector;
    66      private LoggerContext loggerContext;
    67
    68      private Log4jWebInitializerImpl(final ServletContext servletContext) {
    69          this.servletContext = servletContext;
    70          this.map.put("hostName", NetUtils.getLocalHostname());
    71      }
    72
    73      /**
    74       * Initializes the Log4jWebLifeCycle attribute of a ServletContext. Those who wish to obtain this object should
    75       * use the {@link org.apache.logging.log4j.web.WebLoggerContextUtils#getWebLifeCycle(javax.servlet.ServletContext)}
    76       * method instead.
    77       *
    78       * @param servletContext 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 =
    100                     this.substitutor.replace(this.servletContext.getInitParameter(LOG4J_CONFIG_LOCATION));
    101             final boolean isJndi =
    102                     "true".equalsIgnoreCase(this.servletContext.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         }
    150
    151         if (this.name == null && location == null) {
    152             LOGGER.error("No Log4j context configuration provided. This is very unusual.");
    153             return;
    154         }
    155
    156         final URI uri = getConfigURI(location);
    157         this.loggerContext = Configurator.initialize(this.name, this.getClassLoader(), uri, this.servletContext);
    158     }
    159
    160     private URI getConfigURI(final String location) {
    161         try {
    162             String configLocation = location;
    163             if (configLocation == null) {
    164                 final String[] paths = SetUtils.prefixSet(servletContext.getResourcePaths("/WEB-INF/"), "/WEB-INF/log4j2");
    165                 if (paths.length == 1) {
    166                     configLocation = paths[0];
    167                 } else if (paths.length > 1) {
    168                     final String prefix = "/WEB-INF/log4j2-" + this.name + ".";
    169                     boolean found = false;
    170                     for (final String str : paths) {
    171                         if (str.startsWith(prefix)) {
    172                             configLocation = str;
    173                             found = true;
    174                             break;
    175                         }
    176                     }
    177                     if (!found) {
    178                         configLocation = paths[0];
    179                     }
    180                 }
    181             }
    182             if (configLocation != null) {
    183                 final URL url = servletContext.getResource(configLocation);
    184                 if (url != null) {
    185                     return url.toURI();
    186                 }
    187             }
    188         } catch (final Exception ex) {
    189             // Just try passing the location.
    190         }
    191         if (location != null) {
    192             try {
    193                 return FileUtils.getCorrectedFilePathUri(location);
    194             } catch (final Exception e) {
    195                 LOGGER.error("Unable to convert configuration location [{}] to a URI", location, e);
    196             }
    197         }
    198         return null;
    199     }
    200
    201     @Override
    202     public synchronized void stop() {
    203         if (!this.isStarted() && !this.isStopped()) {
    204             throw new IllegalStateException("Cannot stop this Log4jWebInitializer because it has not started.");
    205         }
    206
    207         // only do this once
    208         if (this.isStarted()) {
    209             this.setStopping();
    210             if (this.loggerContext != null) {
    211                 LOGGER.debug("Removing LoggerContext for [{}].", this.name);
    212                 this.servletContext.removeAttribute(CONTEXT_ATTRIBUTE);
    213                 if (this.namedContextSelector != null) {
    214                     this.namedContextSelector.removeContext(this.name);
    215                 }
    216                 this.loggerContext.stop();
    217                 this.loggerContext.setExternalContext(null);
    218                 this.loggerContext = null;
    219             }
    220             this.setStopped();
    221         }
    222     }
    223
    224     @Override
    225     public void setLoggerContext() {
    226         if (this.loggerContext != null) {
    227             ContextAnchor.THREAD_CONTEXT.set(this.loggerContext);
    228         }
    229     }
    230
    231     @Override
    232     public void clearLoggerContext() {
    233         ContextAnchor.THREAD_CONTEXT.remove();
    234     }
    235
    236     @Override
    237     public void wrapExecution(final Runnable runnable) {
    238         this.setLoggerContext();
    239
    240         try {
    241             runnable.run();
    242         } finally {
    243             this.clearLoggerContext();
    244         }
    245     }
    246
    247     private ClassLoader getClassLoader() {
    248         try {
    249             // if container is Servlet 3.0, use its getClassLoader method
    250             // this may look odd, but the call below will throw NoSuchMethodError if user is on Servlet 2.5
    251             // we compile against 3.0 to support Log4jServletContainerInitializer, but we don't require 3.0
    252             return this.servletContext.getClassLoader();
    253         } catch (final Throwable ignore) {
    254             // otherwise, use this class's class loader
    255             return Log4jWebInitializerImpl.class.getClassLoader();
    256         }
    257     }
    258
    259 }