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.concurrent.ConcurrentHashMap;
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;
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.jmx.Server;
39  import org.apache.logging.log4j.core.util.Assert;
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  
47  import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER;
48  
49  /**
50   * The LoggerContext is the anchor for the logging system. It maintains a list
51   * of all the loggers requested by applications and a reference to the
52   * Configuration. The Configuration will contain the configured loggers,
53   * appenders, filters, etc and will be atomically updated whenever a reconfigure
54   * occurs.
55   */
56  public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener {
57  
58      private static final long serialVersionUID = 1L;
59  
60      public static final String PROPERTY_CONFIG = "config";
61      private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
62  
63      private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
64      private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
65  
66      /**
67       * The Configuration is volatile to guarantee that initialization of the
68       * Configuration has completed before the reference is updated.
69       */
70      private volatile Configuration config = new DefaultConfiguration();
71      private Object externalContext;
72      private final String name;
73      private volatile URI configLocation;
74      private Cancellable shutdownCallback;
75  
76      private final Lock configLock = new ReentrantLock();
77  
78      /**
79       * Constructor taking only a name.
80       * @param name The context name.
81       */
82      public LoggerContext(final String name) {
83          this(name, null, (URI) null);
84      }
85  
86      /**
87       * Constructor taking a name and a reference to an external context.
88       * @param name The context name.
89       * @param externalContext The external context.
90       */
91      public LoggerContext(final String name, final Object externalContext) {
92          this(name, externalContext, (URI) null);
93      }
94  
95      /**
96       * Constructor taking a name, external context and a configuration URI.
97       * @param name The context name.
98       * @param externalContext The external context.
99       * @param configLocn The location of the configuration as a URI.
100      */
101     public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
102         this.name = name;
103         this.externalContext = externalContext;
104         this.configLocation = configLocn;
105     }
106 
107     /**
108      * Constructor taking a name external context and a configuration location
109      * String. The location must be resolvable to a File.
110      *
111      * @param name The configuration location.
112      * @param externalContext The external context.
113      * @param configLocn The configuration location.
114      */
115     public LoggerContext(final String name, final Object externalContext, final String configLocn) {
116         this.name = name;
117         this.externalContext = externalContext;
118         if (configLocn != null) {
119             URI uri;
120             try {
121                 uri = new File(configLocn).toURI();
122             } catch (final Exception ex) {
123                 uri = null;
124             }
125             configLocation = uri;
126         } else {
127             configLocation = null;
128         }
129     }
130 
131     @Override
132     public void start() {
133         LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
134         if (configLock.tryLock()) {
135             try {
136                 if (this.isInitialized() || this.isStopped()) {
137                     this.setStarting();
138                     reconfigure();
139                     if (this.config.isShutdownHookEnabled()) {
140                         setUpShutdownHook();
141                     }
142                     this.setStarted();
143                 }
144             } finally {
145                 configLock.unlock();
146             }
147         }
148         LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
149     }
150 
151     /**
152      * Starts with a specific configuration.
153      * @param config The new Configuration.
154      */
155     public void start(final Configuration config) {
156         LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
157         if (configLock.tryLock()) {
158             try {
159                 if (this.isInitialized() || this.isStopped()) {
160                     if (this.config.isShutdownHookEnabled()) {
161                         setUpShutdownHook();
162                     }
163                     this.setStarted();
164                 }
165             } finally {
166                 configLock.unlock();
167             }
168         }
169         setConfiguration(config);
170         LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
171     }
172 
173     private void setUpShutdownHook() {
174         if (shutdownCallback == null) {
175             final LoggerContextFactory factory = LogManager.getFactory();
176             if (factory instanceof ShutdownCallbackRegistry) {
177                 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one.");
178                 try {
179                     this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() {
180                         @Override
181                         public void run() {
182                             final LoggerContext context = LoggerContext.this;
183                             LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]", context.getName(),
184                                 context);
185                             context.stop();
186                         }
187 
188                         @Override
189                         public String toString() {
190                             return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']';
191                         }
192                     });
193                 } catch (final IllegalStateException ise) {
194                     LOGGER.fatal(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook because JVM is shutting down.");
195                 } catch (final SecurityException se) {
196                     LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions");
197                 }
198             }
199         }
200     }
201 
202     @Override
203     public void stop() {
204         LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
205         configLock.lock();
206         try {
207             if (this.isStopped()) {
208                 return;
209             }
210 
211             this.setStopping();
212             try {
213                 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
214             } catch (final Exception ex) {
215                 LOGGER.error("Unable to unregister MBeans", ex);
216             }
217             if (shutdownCallback != null) {
218                 shutdownCallback.cancel();
219                 shutdownCallback = null;
220             }
221             final Configuration prev = config;
222             config = NULL_CONFIGURATION;
223             updateLoggers();
224             prev.stop();
225             externalContext = null;
226             LogManager.getFactory().removeContext(this);
227             this.setStopped();
228         } finally {
229             configLock.unlock();
230         }
231         LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this);
232     }
233 
234     /**
235      * Gets the name.
236      *
237      * @return the name.
238      */
239     public String getName() {
240         return name;
241     }
242 
243     /**
244      * Sets the external context.
245      * @param context The external context.
246      */
247     public void setExternalContext(final Object context) {
248         this.externalContext = context;
249     }
250 
251     /**
252      * Returns the external context.
253      * @return The external context.
254      */
255     @Override
256     public Object getExternalContext() {
257         return this.externalContext;
258     }
259 
260     /**
261      * Obtains a Logger from the Context.
262      * @param name The name of the Logger to return.
263      * @return The Logger.
264      */
265     @Override
266     public Logger getLogger(final String name) {
267         return getLogger(name, null);
268     }
269 
270     /**
271      * Gets a collection of the current loggers.
272      * <p>
273      * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this collection at your own
274      * risk.
275      * </p>
276      *
277      * @return a collection of the current loggers.
278      */
279     public Collection<Logger> getLoggers() {
280         return loggers.values();
281     }
282 
283     /**
284      * Obtains a Logger from the Context.
285      * @param name The name of the Logger to return.
286      * @param messageFactory The message factory is used only when creating a
287      *            logger, subsequent use does not change the logger but will log
288      *            a warning if mismatched.
289      * @return The Logger.
290      */
291     @Override
292     public Logger getLogger(final String name, final MessageFactory messageFactory) {
293         Logger logger = loggers.get(name);
294         if (logger != null) {
295             AbstractLogger.checkMessageFactory(logger, messageFactory);
296             return logger;
297         }
298 
299         logger = newInstance(this, name, messageFactory);
300         final Logger prev = loggers.putIfAbsent(name, logger);
301         return prev == null ? logger : prev;
302     }
303 
304     /**
305      * Determines if the specified Logger exists.
306      * @param name The Logger name to search for.
307      * @return True if the Logger exists, false otherwise.
308      */
309     @Override
310     public boolean hasLogger(final String name) {
311         return loggers.containsKey(name);
312     }
313 
314     /**
315      * Returns the current Configuration. The Configuration will be replaced
316      * when a reconfigure occurs.
317      *
318      * @return The Configuration.
319      */
320     public Configuration getConfiguration() {
321         return config;
322     }
323 
324     /**
325      * Adds a Filter to the Configuration. Filters that are added through the API will be lost
326      * when a reconfigure occurs.
327      * @param filter The Filter to add.
328      */
329     public void addFilter(final Filter filter) {
330         config.addFilter(filter);
331     }
332 
333     /**
334      * Removes a Filter from the current Configuration.
335      * @param filter The Filter to remove.
336      */
337     public void removeFilter(final Filter filter) {
338         config.removeFilter(filter);
339     }
340 
341     /**
342      * Sets the Configuration to be used.
343      * @param config The new Configuration.
344      * @return The previous Configuration.
345      */
346     private Configuration setConfiguration(final Configuration config) {
347         Assert.requireNonNull(config, "No Configuration was provided");
348         configLock.lock();
349         try {
350             final Configuration prev = this.config;
351             config.addListener(this);
352             final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
353 
354             try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
355                 map.putIfAbsent("hostName", NetUtils.getLocalHostname());
356             } catch (final Exception ex) {
357                 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
358                 map.putIfAbsent("hostName", "unknown");
359             }
360             map.putIfAbsent("contextName", name);
361             config.start();
362             this.config = config;
363             updateLoggers();
364             if (prev != null) {
365                 prev.removeListener(this);
366                 prev.stop();
367             }
368 
369             firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
370 
371             try {
372                 Server.reregisterMBeansAfterReconfigure();
373             } catch (final Throwable t) {
374                 // LOG4J2-716: Android has no java.lang.management
375                 LOGGER.error("Could not reconfigure JMX", t);
376             }
377             return prev;
378         } finally {
379             configLock.unlock();
380         }
381     }
382 
383     private void firePropertyChangeEvent(final PropertyChangeEvent event) {
384         for (final PropertyChangeListener listener : propertyChangeListeners) {
385             listener.propertyChange(event);
386         }
387     }
388 
389     public void addPropertyChangeListener(final PropertyChangeListener listener) {
390         propertyChangeListeners.add(Assert.requireNonNull(listener, "listener"));
391     }
392 
393     public void removePropertyChangeListener(final PropertyChangeListener listener) {
394         propertyChangeListeners.remove(listener);
395     }
396 
397     /**
398      * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
399      * current configuration. Use
400      * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link
401      * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration.
402      * @return the initial configuration location or {@code null}
403      */
404     public URI getConfigLocation() {
405         return configLocation;
406     }
407 
408     /**
409      * Sets the configLocation to the specified value and reconfigures this context.
410      * @param configLocation the location of the new configuration
411      */
412     public void setConfigLocation(final URI configLocation) {
413         this.configLocation = configLocation;
414 
415         reconfigure(configLocation);
416     }
417 
418     /**
419      * Reconfigure the context.
420      */
421     private void reconfigure(final URI configURI) {
422         final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
423         LOGGER.debug("Reconfiguration started for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
424                 configURI, this, cl);
425         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configURI, cl);
426         setConfiguration(instance);
427         /*
428          * instance.start(); Configuration old = setConfiguration(instance);
429          * updateLoggers(); if (old != null) { old.stop(); }
430          */
431 
432         LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
433                 configURI, this, cl);
434     }
435 
436     /**
437      * Reconfigure the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
438      * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
439      * LoggerConfig, along with old Appenders and Filters.
440      */
441     public void reconfigure() {
442         reconfigure(configLocation);
443     }
444 
445     /**
446      * Causes all Loggers to be updated against the current Configuration.
447      */
448     public void updateLoggers() {
449         updateLoggers(this.config);
450     }
451 
452     /**
453      * Causes all Logger to be updated against the specified Configuration.
454      * @param config The Configuration.
455      */
456     public void updateLoggers(final Configuration config) {
457         for (final Logger logger : loggers.values()) {
458             logger.updateConfiguration(config);
459         }
460     }
461 
462     /**
463      * Causes a reconfiguration to take place when the underlying configuration
464      * file changes.
465      *
466      * @param reconfigurable The Configuration that can be reconfigured.
467      */
468     @Override
469     public synchronized void onChange(final Reconfigurable reconfigurable) {
470         LOGGER.debug("Reconfiguration started for context {} ({})", name, this);
471         final Configuration newConfig = reconfigurable.reconfigure();
472         if (newConfig != null) {
473             setConfiguration(newConfig);
474             LOGGER.debug("Reconfiguration completed for {} ({})", name, this);
475         } else {
476             LOGGER.debug("Reconfiguration failed for {} ({})", name, this);
477         }
478     }
479 
480     // LOG4J2-151: changed visibility from private to protected
481     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
482         return new Logger(ctx, name, messageFactory);
483     }
484 
485 }