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.config.Configuration;
33  import org.apache.logging.log4j.core.config.ConfigurationFactory;
34  import org.apache.logging.log4j.core.config.ConfigurationListener;
35  import org.apache.logging.log4j.core.config.ConfigurationSource; // SUPPRESS CHECKSTYLE
36  import org.apache.logging.log4j.core.config.DefaultConfiguration;
37  import org.apache.logging.log4j.core.config.NullConfiguration;
38  import org.apache.logging.log4j.core.config.Reconfigurable;
39  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
40  import org.apache.logging.log4j.core.jmx.Server;
41  import org.apache.logging.log4j.core.util.Cancellable;
42  import org.apache.logging.log4j.core.util.NanoClockFactory;
43  import org.apache.logging.log4j.core.util.NetUtils;
44  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
45  import org.apache.logging.log4j.message.MessageFactory;
46  import org.apache.logging.log4j.spi.AbstractLogger;
47  import org.apache.logging.log4j.spi.LoggerContextFactory;
48  import org.apache.logging.log4j.spi.LoggerContextKey;
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 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.error(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      * Gets the root logger.
324      * 
325      * @return the root logger.
326      */
327     public Logger getRootLogger() {
328         return getLogger(LogManager.ROOT_LOGGER_NAME);
329     }
330     
331     /**
332      * Sets the name.
333      * 
334      * @param name the new LoggerContext name
335      * @throws NullPointerException if the specified name is {@code null}
336      */
337     public void setName(final String name) {
338     	contextName = Objects.requireNonNull(name);
339     }
340 
341     /**
342      * Sets the external context.
343      * 
344      * @param context The external context.
345      */
346     public void setExternalContext(final Object context) {
347         this.externalContext = context;
348     }
349 
350     /**
351      * Returns the external context.
352      * 
353      * @return The external context.
354      */
355     @Override
356     public Object getExternalContext() {
357         return this.externalContext;
358     }
359 
360     /**
361      * Gets a Logger from the Context.
362      * 
363      * @param name The name of the Logger to return.
364      * @return The Logger.
365      */
366     @Override
367     public Logger getLogger(final String name) {
368         return getLogger(name, null);
369     }
370 
371     /**
372      * Gets a collection of the current loggers.
373      * <p>
374      * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this
375      * collection at your own risk.
376      * </p>
377      *
378      * @return a collection of the current loggers.
379      */
380     public Collection<Logger> getLoggers() {
381         return loggers.values();
382     }
383 
384     /**
385      * Obtains a Logger from the Context.
386      * 
387      * @param name The name of the Logger to return.
388      * @param messageFactory The message factory is used only when creating a logger, subsequent use does not change the
389      *            logger but will log a warning if mismatched.
390      * @return The Logger.
391      */
392     @Override
393     public Logger getLogger(final String name, final MessageFactory messageFactory) {
394         // Note: This is the only method where we add entries to the 'loggers' ivar. 
395         // The loggers map key is the logger name plus the messageFactory FQCN.
396         String key = LoggerContextKey.create(name, messageFactory);
397         Logger logger = loggers.get(key);
398         if (logger != null) {
399             AbstractLogger.checkMessageFactory(logger, messageFactory);
400             return logger;
401         }
402 
403         logger = newInstance(this, name, messageFactory);
404         // If messageFactory was null then we need to pull it out of the logger now
405         key = LoggerContextKey.create(name, logger.getMessageFactory());
406         final Logger prev = loggers.putIfAbsent(key, logger);
407         return prev == null ? logger : prev;
408     }
409 
410     /**
411      * Determines if the specified Logger exists.
412      * 
413      * @param name The Logger name to search for.
414      * @return True if the Logger exists, false otherwise.
415      */
416     @Override
417     public boolean hasLogger(final String name) {
418         return loggers.containsKey(LoggerContextKey.create(name));
419     }
420 
421     /**
422      * Determines if the specified Logger exists.
423      * 
424      * @param name The Logger name to search for.
425      * @return True if the Logger exists, false otherwise.
426      */
427     @Override
428     public boolean hasLogger(final String name, MessageFactory messageFactory) {
429         return loggers.containsKey(LoggerContextKey.create(name, messageFactory));
430     }
431 
432     /**
433      * Determines if the specified Logger exists.
434      * 
435      * @param name The Logger name to search for.
436      * @return True if the Logger exists, false otherwise.
437      */
438     @Override
439     public boolean hasLogger(final String name, Class<? extends MessageFactory> messageFactoryClass) {
440         return loggers.containsKey(LoggerContextKey.create(name, messageFactoryClass));
441     }
442 
443     /**
444      * Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs.
445      *
446      * @return The Configuration.
447      */
448     public Configuration getConfiguration() {
449         return configuration;
450     }
451 
452     /**
453      * Adds a Filter to the Configuration. Filters that are added through the API will be lost when a reconfigure
454      * occurs.
455      * 
456      * @param filter The Filter to add.
457      */
458     public void addFilter(final Filter filter) {
459         configuration.addFilter(filter);
460     }
461 
462     /**
463      * Removes a Filter from the current Configuration.
464      * 
465      * @param filter The Filter to remove.
466      */
467     public void removeFilter(final Filter filter) {
468         configuration.removeFilter(filter);
469     }
470 
471     /**
472      * Sets the Configuration to be used.
473      * 
474      * @param config The new Configuration.
475      * @return The previous Configuration.
476      */
477     private Configuration setConfiguration(final Configuration config) {
478         Objects.requireNonNull(config, "No Configuration was provided");
479         configLock.lock();
480         try {
481             final Configuration prev = this.configuration;
482             config.addListener(this);
483             final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
484 
485             try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
486                 map.putIfAbsent("hostName", NetUtils.getLocalHostname());
487             } catch (final Exception ex) {
488                 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
489                 map.putIfAbsent("hostName", "unknown");
490             }
491             map.putIfAbsent("contextName", contextName);
492             config.start();
493             this.configuration = config;
494             updateLoggers();
495             if (prev != null) {
496                 prev.removeListener(this);
497                 prev.stop();
498             }
499 
500             firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
501 
502             try {
503                 Server.reregisterMBeansAfterReconfigure();
504             } catch (final Throwable t) {
505                 // LOG4J2-716: Android has no java.lang.management
506                 LOGGER.error("Could not reconfigure JMX", t);
507             }
508             // AsyncLoggers update their nanoClock when the configuration changes
509             Log4jLogEvent.setNanoClock(NanoClockFactory.createNanoClock());
510             
511             return prev;
512         } finally {
513             configLock.unlock();
514         }
515     }
516 
517     private void firePropertyChangeEvent(final PropertyChangeEvent event) {
518         for (final PropertyChangeListener listener : propertyChangeListeners) {
519             listener.propertyChange(event);
520         }
521     }
522 
523     public void addPropertyChangeListener(final PropertyChangeListener listener) {
524         propertyChangeListeners.add(Objects.requireNonNull(listener, "listener"));
525     }
526 
527     public void removePropertyChangeListener(final PropertyChangeListener listener) {
528         propertyChangeListeners.remove(listener);
529     }
530 
531     /**
532      * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
533      * current configuration. Use {@link #getConfiguration()}.{@link Configuration#getConfigurationSource()
534      * getConfigurationSource()}.{@link ConfigurationSource#getLocation() getLocation()} to get the actual source of the
535      * current configuration.
536      * 
537      * @return the initial configuration location or {@code null}
538      */
539     public URI getConfigLocation() {
540         return configLocation;
541     }
542 
543     /**
544      * Sets the configLocation to the specified value and reconfigures this context.
545      * 
546      * @param configLocation the location of the new configuration
547      */
548     public void setConfigLocation(final URI configLocation) {
549         this.configLocation = configLocation;
550 
551         reconfigure(configLocation);
552     }
553 
554     /**
555      * Reconfigures the context.
556      */
557     private void reconfigure(final URI configURI) {
558         final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
559         LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
560                 contextName, configURI, this, cl);
561         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(contextName, configURI, cl);
562         setConfiguration(instance);
563         /*
564          * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old != null) {
565          * old.stop(); }
566          */
567         final String location = configuration == null ? "?" : String.valueOf(configuration.getConfigurationSource());
568         LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
569                 contextName, location, this, cl);
570     }
571 
572     /**
573      * Reconfigures the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
574      * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
575      * LoggerConfig, along with old Appenders and Filters.
576      */
577     public void reconfigure() {
578         reconfigure(configLocation);
579     }
580 
581     /**
582      * Causes all Loggers to be updated against the current Configuration.
583      */
584     public void updateLoggers() {
585         updateLoggers(this.configuration);
586     }
587 
588     /**
589      * Causes all Logger to be updated against the specified Configuration.
590      * 
591      * @param config The Configuration.
592      */
593     public void updateLoggers(final Configuration config) {
594         for (final Logger logger : loggers.values()) {
595             logger.updateConfiguration(config);
596         }
597     }
598 
599     /**
600      * Causes a reconfiguration to take place when the underlying configuration file changes.
601      *
602      * @param reconfigurable The Configuration that can be reconfigured.
603      */
604     @Override
605     public synchronized void onChange(final Reconfigurable reconfigurable) {
606         LOGGER.debug("Reconfiguration started for context {} ({})", contextName, this);
607         final Configuration newConfig = reconfigurable.reconfigure();
608         if (newConfig != null) {
609             setConfiguration(newConfig);
610             LOGGER.debug("Reconfiguration completed for {} ({})", contextName, this);
611         } else {
612             LOGGER.debug("Reconfiguration failed for {} ({})", contextName, this);
613         }
614     }
615 
616     // LOG4J2-151: changed visibility from private to protected
617     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
618         return new Logger(ctx, name, messageFactory);
619     }
620 
621 }