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