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