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;
18  
19  import java.beans.PropertyChangeEvent;
20  import java.beans.PropertyChangeListener;
21  import java.io.File;
22  import java.net.URI;
23  import java.util.Collection;
24  import java.util.Objects;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.ConcurrentMap;
27  import java.util.concurrent.CopyOnWriteArrayList;
28  import java.util.concurrent.locks.Lock;
29  import java.util.concurrent.locks.ReentrantLock;
30  
31  import org.apache.logging.log4j.LogManager;
32  import org.apache.logging.log4j.core.async.AsyncLogger;
33  import org.apache.logging.log4j.core.config.Configuration;
34  import org.apache.logging.log4j.core.config.ConfigurationFactory;
35  import org.apache.logging.log4j.core.config.ConfigurationListener;
36  import org.apache.logging.log4j.core.config.ConfigurationSource; // SUPPRESS CHECKSTYLE
37  import org.apache.logging.log4j.core.config.DefaultConfiguration;
38  import org.apache.logging.log4j.core.config.NullConfiguration;
39  import org.apache.logging.log4j.core.config.Reconfigurable;
40  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
41  import org.apache.logging.log4j.core.jmx.Server;
42  import org.apache.logging.log4j.core.util.Cancellable;
43  import org.apache.logging.log4j.core.util.NanoClockFactory;
44  import org.apache.logging.log4j.core.util.NetUtils;
45  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
46  import org.apache.logging.log4j.message.MessageFactory;
47  import org.apache.logging.log4j.spi.AbstractLogger;
48  import org.apache.logging.log4j.spi.LoggerContextFactory;
49  
50  import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.*;
51  
52  /**
53   * The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by
54   * applications and a reference to the Configuration. The Configuration will contain the configured loggers, appenders,
55   * filters, etc and will be atomically updated whenever a reconfigure occurs.
56   */
57  public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext,
58          ConfigurationListener {
59  
60      /**
61       * Property name of the property change event fired if the configuration is changed.
62       */
63      public static final String PROPERTY_CONFIG = "config";
64  
65      private static final long serialVersionUID = 1L;
66      private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
67  
68      private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<>();
69      private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>();
70  
71      /**
72       * The Configuration is volatile to guarantee that initialization of the Configuration has completed before the
73       * reference is updated.
74       */
75      private volatile Configuration configuration = new DefaultConfiguration();
76      private Object externalContext;
77      private final String contextName;
78      private volatile URI configLocation;
79      private Cancellable shutdownCallback;
80  
81      private final Lock configLock = new ReentrantLock();
82  
83      /**
84       * Constructor taking only a name.
85       * 
86       * @param name The context name.
87       */
88      public LoggerContext(final String name) {
89          this(name, null, (URI) null);
90      }
91  
92      /**
93       * Constructor taking a name and a reference to an external context.
94       * 
95       * @param name The context name.
96       * @param externalContext The external context.
97       */
98      public LoggerContext(final String name, final Object externalContext) {
99          this(name, externalContext, (URI) null);
100     }
101 
102     /**
103      * Constructor taking a name, external context and a configuration URI.
104      * 
105      * @param name The context name.
106      * @param externalContext The external context.
107      * @param configLocn The location of the configuration as a URI.
108      */
109     public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
110         this.contextName = name;
111         this.externalContext = externalContext;
112         this.configLocation = configLocn;
113     }
114 
115     /**
116      * Constructor taking a name external context and a configuration location String. The location must be resolvable
117      * to a File.
118      *
119      * @param name The configuration location.
120      * @param externalContext The external context.
121      * @param configLocn The configuration location.
122      */
123     public LoggerContext(final String name, final Object externalContext, final String configLocn) {
124         this.contextName = name;
125         this.externalContext = externalContext;
126         if (configLocn != null) {
127             URI uri;
128             try {
129                 uri = new File(configLocn).toURI();
130             } catch (final Exception ex) {
131                 uri = null;
132             }
133             configLocation = uri;
134         } else {
135             configLocation = null;
136         }
137     }
138 
139     /**
140      * Returns the current LoggerContext.
141      * <p>
142      * Avoids the type cast for:
143      * </p>
144      *
145      * <pre>
146      * (LoggerContext) LogManager.getContext();
147      * </pre>
148      *
149      * <p>
150      * WARNING - The LoggerContext returned by this method may not be the LoggerContext used to create a Logger for the
151      * calling class.
152      * </p>
153      * 
154      * @return The current LoggerContext.
155      * @see LogManager#getContext()
156      */
157     public static LoggerContext getContext() {
158         return (LoggerContext) LogManager.getContext();
159     }
160 
161     /**
162      * Returns a LoggerContext.
163      * <p>
164      * Avoids the type cast for:
165      * </p>
166      *
167      * <pre>
168      * (LoggerContext) LogManager.getContext(currentContext);
169      * </pre>
170      *
171      * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
172      *            example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
173      *            returned and if the caller is a class in the container's classpath then a different LoggerContext may
174      *            be returned. If true then only a single LoggerContext will be returned.
175      * @return a LoggerContext.
176      * @see LogManager#getContext(boolean)
177      */
178     public static LoggerContext getContext(final boolean currentContext) {
179         return (LoggerContext) LogManager.getContext(currentContext);
180     }
181 
182     /**
183      * Returns a LoggerContext.
184      * <p>
185      * Avoids the type cast for:
186      * </p>
187      *
188      * <pre>
189      * (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
190      * </pre>
191      *
192      * @param loader The ClassLoader for the context. If null the context will attempt to determine the appropriate
193      *            ClassLoader.
194      * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
195      *            example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
196      *            returned and if the caller is a class in the container's classpath then a different LoggerContext may
197      *            be returned. If true then only a single LoggerContext will be returned.
198      * @param configLocation The URI for the configuration to use.
199      * @return a LoggerContext.
200      * @see LogManager#getContext(ClassLoader, boolean, URI)
201      */
202     public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext,
203             final URI configLocation) {
204         return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
205     }
206 
207     @Override
208     public void start() {
209         LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
210         if (configLock.tryLock()) {
211             try {
212                 if (this.isInitialized() || this.isStopped()) {
213                     this.setStarting();
214                     reconfigure();
215                     if (this.configuration.isShutdownHookEnabled()) {
216                         setUpShutdownHook();
217                     }
218                     this.setStarted();
219                 }
220             } finally {
221                 configLock.unlock();
222             }
223         }
224         LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
225     }
226 
227     /**
228      * Starts with a specific configuration.
229      * 
230      * @param config The new Configuration.
231      */
232     public void start(final Configuration config) {
233         LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
234         if (configLock.tryLock()) {
235             try {
236                 if (this.isInitialized() || this.isStopped()) {
237                     if (this.configuration.isShutdownHookEnabled()) {
238                         setUpShutdownHook();
239                     }
240                     this.setStarted();
241                 }
242             } finally {
243                 configLock.unlock();
244             }
245         }
246         setConfiguration(config);
247         LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
248     }
249 
250     private void setUpShutdownHook() {
251         if (shutdownCallback == null) {
252             final LoggerContextFactory factory = LogManager.getFactory();
253             if (factory instanceof ShutdownCallbackRegistry) {
254                 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one.");
255                 try {
256                     this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() {
257                         @Override
258                         public void run() {
259                             final LoggerContext context = LoggerContext.this;
260                             LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]",
261                                     context.getName(), context);
262                             context.stop();
263                         }
264 
265                         @Override
266                         public String toString() {
267                             return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']';
268                         }
269                     });
270                 } catch (final IllegalStateException e) {
271                     LOGGER.fatal(SHUTDOWN_HOOK_MARKER,
272                             "Unable to register shutdown hook because JVM is shutting down.", e);
273                 } catch (final SecurityException e) {
274                     LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions",
275                             e);
276                 }
277             }
278         }
279     }
280 
281     @Override
282     public void stop() {
283         LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
284         configLock.lock();
285         try {
286             if (this.isStopped()) {
287                 return;
288             }
289 
290             this.setStopping();
291             try {
292                 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
293             } catch (final Exception ex) {
294                 LOGGER.error("Unable to unregister MBeans", ex);
295             }
296             if (shutdownCallback != null) {
297                 shutdownCallback.cancel();
298                 shutdownCallback = null;
299             }
300             final Configuration prev = configuration;
301             configuration = NULL_CONFIGURATION;
302             updateLoggers();
303             prev.stop();
304             externalContext = null;
305             LogManager.getFactory().removeContext(this);
306             this.setStopped();
307         } finally {
308             configLock.unlock();
309         }
310         LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this);
311     }
312 
313     /**
314      * Gets the name.
315      *
316      * @return the name.
317      */
318     public String getName() {
319         return contextName;
320     }
321 
322     /**
323      * Sets the external context.
324      * 
325      * @param context The external context.
326      */
327     public void setExternalContext(final Object context) {
328         this.externalContext = context;
329     }
330 
331     /**
332      * Returns the external context.
333      * 
334      * @return The external context.
335      */
336     @Override
337     public Object getExternalContext() {
338         return this.externalContext;
339     }
340 
341     /**
342      * Obtains a Logger from the Context.
343      * 
344      * @param name The name of the Logger to return.
345      * @return The Logger.
346      */
347     @Override
348     public Logger getLogger(final String name) {
349         return getLogger(name, null);
350     }
351 
352     /**
353      * Gets a collection of the current loggers.
354      * <p>
355      * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this
356      * collection at your own risk.
357      * </p>
358      *
359      * @return a collection of the current loggers.
360      */
361     public Collection<Logger> getLoggers() {
362         return loggers.values();
363     }
364 
365     /**
366      * Obtains a Logger from the Context.
367      * 
368      * @param name The name of the Logger to return.
369      * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the
370      *            logger but will log a warning if mismatched.
371      * @return The Logger.
372      */
373     @Override
374     public Logger getLogger(final String name, final MessageFactory messageFactory) {
375         Logger logger = loggers.get(name);
376         if (logger != null) {
377             AbstractLogger.checkMessageFactory(logger, messageFactory);
378             return logger;
379         }
380 
381         logger = newInstance(this, name, messageFactory);
382         final Logger prev = loggers.putIfAbsent(name, logger);
383         return prev == null ? logger : prev;
384     }
385 
386     /**
387      * Determines if the specified Logger exists.
388      * 
389      * @param name The Logger name to search for.
390      * @return True if the Logger exists, false otherwise.
391      */
392     @Override
393     public boolean hasLogger(final String name) {
394         return loggers.containsKey(name);
395     }
396 
397     /**
398      * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs.
399      *
400      * @return The Configuration.
401      */
402     public Configuration getConfiguration() {
403         return configuration;
404     }
405 
406     /**
407      * Adds a Filter to the Configuration. Filters that are added through the API will be lost when a reconfigure
408      * occurs.
409      * 
410      * @param filter The Filter to add.
411      */
412     public void addFilter(final Filter filter) {
413         configuration.addFilter(filter);
414     }
415 
416     /**
417      * Removes a Filter from the current Configuration.
418      * 
419      * @param filter The Filter to remove.
420      */
421     public void removeFilter(final Filter filter) {
422         configuration.removeFilter(filter);
423     }
424 
425     /**
426      * Sets the Configuration to be used.
427      * 
428      * @param config The new Configuration.
429      * @return The previous Configuration.
430      */
431     private Configuration setConfiguration(final Configuration config) {
432         Objects.requireNonNull(config, "No Configuration was provided");
433         configLock.lock();
434         try {
435             final Configuration prev = this.configuration;
436             config.addListener(this);
437             final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
438 
439             try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
440                 map.putIfAbsent("hostName", NetUtils.getLocalHostname());
441             } catch (final Exception ex) {
442                 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
443                 map.putIfAbsent("hostName", "unknown");
444             }
445             map.putIfAbsent("contextName", contextName);
446             config.start();
447             this.configuration = config;
448             updateLoggers();
449             if (prev != null) {
450                 prev.removeListener(this);
451                 prev.stop();
452             }
453 
454             firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
455 
456             try {
457                 Server.reregisterMBeansAfterReconfigure();
458             } catch (final Throwable t) {
459                 // LOG4J2-716: Android has no java.lang.management
460                 LOGGER.error("Could not reconfigure JMX", t);
461             }
462             Log4jLogEvent.setNanoClock(NanoClockFactory.createNanoClock());
463             try {
464                 AsyncLogger.setNanoClock(NanoClockFactory.createNanoClock());
465             } catch (Throwable ignored) {
466                 // LMAX Disruptor jar may not be in the classpath. Ignore this.
467                 LOGGER.debug("Could not set AsyncLogger NanoClock. Ignoring: " + ignored.toString());
468             }
469             return prev;
470         } finally {
471             configLock.unlock();
472         }
473     }
474 
475     private void firePropertyChangeEvent(final PropertyChangeEvent event) {
476         for (final PropertyChangeListener listener : propertyChangeListeners) {
477             listener.propertyChange(event);
478         }
479     }
480 
481     public void addPropertyChangeListener(final PropertyChangeListener listener) {
482         propertyChangeListeners.add(Objects.requireNonNull(listener, "listener"));
483     }
484 
485     public void removePropertyChangeListener(final PropertyChangeListener listener) {
486         propertyChangeListeners.remove(listener);
487     }
488 
489     /**
490      * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
491      * current configuration. Use {@link #getConfiguration()}.{@link Configuration#getConfigurationSource()
492      * getConfigurationSource()}.{@link ConfigurationSource#getLocation() getLocation()} to get the actual source of the
493      * current configuration.
494      * 
495      * @return the initial configuration location or {@code null}
496      */
497     public URI getConfigLocation() {
498         return configLocation;
499     }
500 
501     /**
502      * Sets the configLocation to the specified value and reconfigures this context.
503      * 
504      * @param configLocation the location of the new configuration
505      */
506     public void setConfigLocation(final URI configLocation) {
507         this.configLocation = configLocation;
508 
509         reconfigure(configLocation);
510     }
511 
512     /**
513      * Reconfigure the context.
514      */
515     private void reconfigure(final URI configURI) {
516         final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
517         LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
518                 contextName, configURI, this, cl);
519         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(contextName, configURI, cl);
520         setConfiguration(instance);
521         /*
522          * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {
523          * old.stop(); }
524          */
525 
526         LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
527                 contextName, configURI, this, cl);
528     }
529 
530     /**
531      * Reconfigure the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
532      * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
533      * LoggerConfig, along with old Appenders and Filters.
534      */
535     public void reconfigure() {
536         reconfigure(configLocation);
537     }
538 
539     /**
540      * Causes all Loggers to be updated against the current Configuration.
541      */
542     public void updateLoggers() {
543         updateLoggers(this.configuration);
544     }
545 
546     /**
547      * Causes all Logger to be updated against the specified Configuration.
548      * 
549      * @param config The Configuration.
550      */
551     public void updateLoggers(final Configuration config) {
552         for (final Logger logger : loggers.values()) {
553             logger.updateConfiguration(config);
554         }
555     }
556 
557     /**
558      * Causes a reconfiguration to take place when the underlying configuration file changes.
559      *
560      * @param reconfigurable The Configuration that can be reconfigured.
561      */
562     @Override
563     public synchronized void onChange(final Reconfigurable reconfigurable) {
564         LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
565         final Configuration newConfig = reconfigurable.reconfigure();
566         if (newConfig != null) {
567             setConfiguration(newConfig);
568             LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
569         } else {
570             LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
571         }
572     }
573 
574     // LOG4J2-151: changed visibility from private to protected
575     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
576         return new Logger(ctx, name, messageFactory);
577     }
578 
579 }