View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core;
18  
19  import java.beans.PropertyChangeEvent;
20  import java.beans.PropertyChangeListener;
21  import java.io.File;
22  import java.net.URI;
23  import java.util.Collection;
24  import java.util.Objects;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.ConcurrentMap;
27  import java.util.concurrent.CopyOnWriteArrayList;
28  import java.util.concurrent.locks.Lock;
29  import java.util.concurrent.locks.ReentrantLock;
30  
31  import org.apache.logging.log4j.LogManager;
32  import org.apache.logging.log4j.core.async.AsyncLogger;
33  import org.apache.logging.log4j.core.config.Configuration;
34  import org.apache.logging.log4j.core.config.ConfigurationFactory;
35  import org.apache.logging.log4j.core.config.ConfigurationListener;
36  import org.apache.logging.log4j.core.config.ConfigurationSource;
37  import org.apache.logging.log4j.core.config.DefaultConfiguration;
38  import org.apache.logging.log4j.core.config.NullConfiguration;
39  import org.apache.logging.log4j.core.config.Reconfigurable;
40  import org.apache.logging.log4j.core.impl.Log4jLogEvent;
41  import org.apache.logging.log4j.core.jmx.Server;
42  import org.apache.logging.log4j.core.util.Cancellable;
43  import org.apache.logging.log4j.core.util.NanoClockFactory;
44  import org.apache.logging.log4j.core.util.NetUtils;
45  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
46  import org.apache.logging.log4j.message.MessageFactory;
47  import org.apache.logging.log4j.spi.AbstractLogger;
48  import org.apache.logging.log4j.spi.LoggerContextFactory;
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
54   * of all the loggers requested by applications and a reference to the
55   * Configuration. The Configuration will contain the configured loggers,
56   * appenders, filters, etc and will be atomically updated whenever a reconfigure
57   * occurs.
58   */
59  public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener {
60  
61      private static final long serialVersionUID = 1L;
62  
63      public static final String PROPERTY_CONFIG = "config";
64      private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
65  
66      /**
67       * Returns the current LoggerContext.
68       * <p>
69       * Avoids the type cast for:
70       *</p>
71       *<pre>(LoggerContext) LogManager.getContext();</pre>
72       *
73       * <p>
74       * WARNING - The LoggerContext returned by this method may not be the LoggerContext used to create a Logger
75       * for the calling class.
76       * </p>
77       * @return  The current LoggerContext.
78       * @see LogManager#getContext()
79       */
80      public static LoggerContext getContext() {
81          return (LoggerContext) LogManager.getContext();
82      }
83  
84      /**
85       * Returns a LoggerContext.
86       * <p>
87       * Avoids the type cast for:
88       *</p>
89       *<pre>(LoggerContext) LogManager.getContext(currentContext);</pre>
90       *
91       * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
92       * example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
93       * returned and if the caller is a class in the container's classpath then a different LoggerContext may be
94       * returned. If true then only a single LoggerContext will be returned.
95       * @return a LoggerContext.
96       * @see LogManager#getContext(boolean)
97       */
98      public static LoggerContext getContext(final boolean currentContext) {
99          return (LoggerContext) LogManager.getContext(currentContext);
100     }
101 
102     /**
103      * Returns a LoggerContext.
104      * <p>
105      * Avoids the type cast for:
106      *</p>
107      *<pre>(LoggerContext) LogManager.getContext(loader, currentContext, configLocation);</pre>
108      *
109      * @param loader The ClassLoader for the context. If null the context will attempt to determine the appropriate
110      * ClassLoader.
111      * @param currentContext if false the LoggerContext appropriate for the caller of this method is returned. For
112      * example, in a web application if the caller is a class in WEB-INF/lib then one LoggerContext may be
113      * returned and if the caller is a class in the container's classpath then a different LoggerContext may be
114      * returned. If true then only a single LoggerContext will be returned.
115      * @param configLocation The URI for the configuration to use.
116      * @return a LoggerContext.
117      * @see LogManager#getContext(ClassLoader, boolean, URI)
118      */
119     public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext,
120             final URI configLocation) {
121         return (LoggerContext) LogManager.getContext(loader, currentContext, configLocation);
122     }
123 
124     private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<>();
125     private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>();
126 
127     /**
128      * The Configuration is volatile to guarantee that initialization of the
129      * Configuration has completed before the reference is updated.
130      */
131     private volatile Configuration config = new DefaultConfiguration();
132     private Object externalContext;
133     private final String name;
134     private volatile URI configLocation;
135     private Cancellable shutdownCallback;
136 
137     private final Lock configLock = new ReentrantLock();
138 
139     /**
140      * Constructor taking only a name.
141      * @param name The context name.
142      */
143     public LoggerContext(final String name) {
144         this(name, null, (URI) null);
145     }
146 
147     /**
148      * Constructor taking a name and a reference to an external context.
149      * @param name The context name.
150      * @param externalContext The external context.
151      */
152     public LoggerContext(final String name, final Object externalContext) {
153         this(name, externalContext, (URI) null);
154     }
155 
156     /**
157      * Constructor taking a name, external context and a configuration URI.
158      * @param name The context name.
159      * @param externalContext The external context.
160      * @param configLocn The location of the configuration as a URI.
161      */
162     public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
163         this.name = name;
164         this.externalContext = externalContext;
165         this.configLocation = configLocn;
166     }
167 
168     /**
169      * Constructor taking a name external context and a configuration location
170      * String. The location must be resolvable to a File.
171      *
172      * @param name The configuration location.
173      * @param externalContext The external context.
174      * @param configLocn The configuration location.
175      */
176     public LoggerContext(final String name, final Object externalContext, final String configLocn) {
177         this.name = name;
178         this.externalContext = externalContext;
179         if (configLocn != null) {
180             URI uri;
181             try {
182                 uri = new File(configLocn).toURI();
183             } catch (final Exception ex) {
184                 uri = null;
185             }
186             configLocation = uri;
187         } else {
188             configLocation = null;
189         }
190     }
191 
192     @Override
193     public void start() {
194         LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
195         if (configLock.tryLock()) {
196             try {
197                 if (this.isInitialized() || this.isStopped()) {
198                     this.setStarting();
199                     reconfigure();
200                     if (this.config.isShutdownHookEnabled()) {
201                         setUpShutdownHook();
202                     }
203                     this.setStarted();
204                 }
205             } finally {
206                 configLock.unlock();
207             }
208         }
209         LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
210     }
211 
212     /**
213      * Starts with a specific configuration.
214      * @param config The new Configuration.
215      */
216     public void start(final Configuration config) {
217         LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
218         if (configLock.tryLock()) {
219             try {
220                 if (this.isInitialized() || this.isStopped()) {
221                     if (this.config.isShutdownHookEnabled()) {
222                         setUpShutdownHook();
223                     }
224                     this.setStarted();
225                 }
226             } finally {
227                 configLock.unlock();
228             }
229         }
230         setConfiguration(config);
231         LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
232     }
233 
234     private void setUpShutdownHook() {
235         if (shutdownCallback == null) {
236             final LoggerContextFactory factory = LogManager.getFactory();
237             if (factory instanceof ShutdownCallbackRegistry) {
238                 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one.");
239                 try {
240                     this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() {
241                         @Override
242                         public void run() {
243                             final LoggerContext context = LoggerContext.this;
244                             LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]", context.getName(),
245                                 context);
246                             context.stop();
247                         }
248 
249                         @Override
250                         public String toString() {
251                             return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']';
252                         }
253                     });
254                 } catch (final IllegalStateException e) {
255                     LOGGER.fatal(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook because JVM is shutting down.", e);
256                 } catch (final SecurityException e) {
257                     LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions", e);
258                 }
259             }
260         }
261     }
262 
263     @Override
264     public void stop() {
265         LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
266         configLock.lock();
267         try {
268             if (this.isStopped()) {
269                 return;
270             }
271 
272             this.setStopping();
273             try {
274                 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
275             } catch (final Exception ex) {
276                 LOGGER.error("Unable to unregister MBeans", ex);
277             }
278             if (shutdownCallback != null) {
279                 shutdownCallback.cancel();
280                 shutdownCallback = null;
281             }
282             final Configuration prev = config;
283             config = NULL_CONFIGURATION;
284             updateLoggers();
285             prev.stop();
286             externalContext = null;
287             LogManager.getFactory().removeContext(this);
288             this.setStopped();
289         } finally {
290             configLock.unlock();
291         }
292         LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this);
293     }
294 
295     /**
296      * Gets the name.
297      *
298      * @return the name.
299      */
300     public String getName() {
301         return name;
302     }
303 
304     /**
305      * Sets the external context.
306      * @param context The external context.
307      */
308     public void setExternalContext(final Object context) {
309         this.externalContext = context;
310     }
311 
312     /**
313      * Returns the external context.
314      * @return The external context.
315      */
316     @Override
317     public Object getExternalContext() {
318         return this.externalContext;
319     }
320 
321     /**
322      * Obtains a Logger from the Context.
323      * @param name The name of the Logger to return.
324      * @return The Logger.
325      */
326     @Override
327     public Logger getLogger(final String name) {
328         return getLogger(name, null);
329     }
330 
331     /**
332      * Gets a collection of the current loggers.
333      * <p>
334      * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this collection at your own
335      * risk.
336      * </p>
337      *
338      * @return a collection of the current loggers.
339      */
340     public Collection<Logger> getLoggers() {
341         return loggers.values();
342     }
343 
344     /**
345      * Obtains a Logger from the Context.
346      * @param name The name of the Logger to return.
347      * @param messageFactory The message factory is used only when creating a
348      *            logger, subsequent use does not change the logger but will log
349      *            a warning if mismatched.
350      * @return The Logger.
351      */
352     @Override
353     public Logger getLogger(final String name, final MessageFactory messageFactory) {
354         Logger logger = loggers.get(name);
355         if (logger != null) {
356             AbstractLogger.checkMessageFactory(logger, messageFactory);
357             return logger;
358         }
359 
360         logger = newInstance(this, name, messageFactory);
361         final Logger prev = loggers.putIfAbsent(name, logger);
362         return prev == null ? logger : prev;
363     }
364 
365     /**
366      * Determines if the specified Logger exists.
367      * @param name The Logger name to search for.
368      * @return True if the Logger exists, false otherwise.
369      */
370     @Override
371     public boolean hasLogger(final String name) {
372         return loggers.containsKey(name);
373     }
374 
375     /**
376      * Returns the current Configuration. The Configuration will be replaced
377      * when a reconfigure occurs.
378      *
379      * @return The Configuration.
380      */
381     public Configuration getConfiguration() {
382         return config;
383     }
384 
385     /**
386      * Adds a Filter to the Configuration. Filters that are added through the API will be lost
387      * when a reconfigure occurs.
388      * @param filter The Filter to add.
389      */
390     public void addFilter(final Filter filter) {
391         config.addFilter(filter);
392     }
393 
394     /**
395      * Removes a Filter from the current Configuration.
396      * @param filter The Filter to remove.
397      */
398     public void removeFilter(final Filter filter) {
399         config.removeFilter(filter);
400     }
401 
402     /**
403      * Sets the Configuration to be used.
404      * @param config The new Configuration.
405      * @return The previous Configuration.
406      */
407     private Configuration setConfiguration(final Configuration config) {
408         Objects.requireNonNull(config, "No Configuration was provided");
409         configLock.lock();
410         try {
411             final Configuration prev = this.config;
412             config.addListener(this);
413             final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
414 
415             try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
416                 map.putIfAbsent("hostName", NetUtils.getLocalHostname());
417             } catch (final Exception ex) {
418                 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
419                 map.putIfAbsent("hostName", "unknown");
420             }
421             map.putIfAbsent("contextName", name);
422             config.start();
423             this.config = config;
424             updateLoggers();
425             if (prev != null) {
426                 prev.removeListener(this);
427                 prev.stop();
428             }
429 
430             firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
431 
432             try {
433                 Server.reregisterMBeansAfterReconfigure();
434             } catch (final Throwable t) {
435                 // LOG4J2-716: Android has no java.lang.management
436                 LOGGER.error("Could not reconfigure JMX", t);
437             }
438             Log4jLogEvent.setNanoClock(NanoClockFactory.createNanoClock());
439             try {
440                 AsyncLogger.setNanoClock(NanoClockFactory.createNanoClock());
441             } catch (Throwable ignored) {
442                 // LMAX Disruptor jar may not be in the classpath. Ignore this.
443                 LOGGER.debug("Could not set AsyncLogger NanoClock. Ignoring: " + ignored.toString());
444             }
445             return prev;
446         } finally {
447             configLock.unlock();
448         }
449     }
450 
451     private void firePropertyChangeEvent(final PropertyChangeEvent event) {
452         for (final PropertyChangeListener listener : propertyChangeListeners) {
453             listener.propertyChange(event);
454         }
455     }
456 
457     public void addPropertyChangeListener(final PropertyChangeListener listener) {
458         propertyChangeListeners.add(Objects.requireNonNull(listener, "listener"));
459     }
460 
461     public void removePropertyChangeListener(final PropertyChangeListener listener) {
462         propertyChangeListeners.remove(listener);
463     }
464 
465     /**
466      * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
467      * current configuration. Use
468      * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link
469      * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration.
470      * @return the initial configuration location or {@code null}
471      */
472     public URI getConfigLocation() {
473         return configLocation;
474     }
475 
476     /**
477      * Sets the configLocation to the specified value and reconfigures this context.
478      * @param configLocation the location of the new configuration
479      */
480     public void setConfigLocation(final URI configLocation) {
481         this.configLocation = configLocation;
482 
483         reconfigure(configLocation);
484     }
485 
486     /**
487      * Reconfigure the context.
488      */
489     private void reconfigure(final URI configURI) {
490         final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
491         LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}", name,
492                 configURI, this, cl);
493         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configURI, cl);
494         setConfiguration(instance);
495         /*
496          * instance.start(); Configuration old = setConfiguration(instance);
497          * updateLoggers(); if (old != null) { old.stop(); }
498          */
499 
500         LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}", name,
501                 configURI, this, cl);
502     }
503 
504     /**
505      * Reconfigure the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
506      * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
507      * LoggerConfig, along with old Appenders and Filters.
508      */
509     public void reconfigure() {
510         reconfigure(configLocation);
511     }
512 
513     /**
514      * Causes all Loggers to be updated against the current Configuration.
515      */
516     public void updateLoggers() {
517         updateLoggers(this.config);
518     }
519 
520     /**
521      * Causes all Logger to be updated against the specified Configuration.
522      * @param config The Configuration.
523      */
524     public void updateLoggers(final Configuration config) {
525         for (final Logger logger : loggers.values()) {
526             logger.updateConfiguration(config);
527         }
528     }
529 
530     /**
531      * Causes a reconfiguration to take place when the underlying configuration
532      * file changes.
533      *
534      * @param reconfigurable The Configuration that can be reconfigured.
535      */
536     @Override
537     public synchronized void onChange(final Reconfigurable reconfigurable) {
538         LOGGER.debug("Reconfiguration started for context {} ({})", name, this);
539         final Configuration newConfig = reconfigurable.reconfigure();
540         if (newConfig != null) {
541             setConfiguration(newConfig);
542             LOGGER.debug("Reconfiguration completed for {} ({})", name, this);
543         } else {
544             LOGGER.debug("Reconfiguration failed for {} ({})", name, this);
545         }
546     }
547 
548     // LOG4J2-151: changed visibility from private to protected
549     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
550         return new Logger(ctx, name, messageFactory);
551     }
552 
553 }