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