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