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 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      * Start 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 (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      * Set 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      * Obtain 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      * Obtain 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      * Determine 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      * Add 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      * Set the Configuration to be used.
343      * @param config The new Configuration.
344      * @return The previous Configuration.
345      */
346     private synchronized Configuration setConfiguration(final Configuration config) {
347         Assert.requireNonNull(config, "No Configuration was provided");
348         final Configuration prev = this.config;
349         config.addListener(this);
350         final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
351 
352         try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
353             map.putIfAbsent("hostName", NetUtils.getLocalHostname());
354         } catch (final Exception ex) {
355             LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
356             map.putIfAbsent("hostName", "unknown");
357         }
358         map.putIfAbsent("contextName", name);
359         config.start();
360         this.config = config;
361         updateLoggers();
362         if (prev != null) {
363             prev.removeListener(this);
364             prev.stop();
365         }
366 
367         firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
368 
369         try {
370             Server.reregisterMBeansAfterReconfigure();
371         } catch (final Throwable t) {
372             // LOG4J2-716: Android has no java.lang.management
373             LOGGER.error("Could not reconfigure JMX", t);
374         }
375         return prev;
376     }
377 
378     private void firePropertyChangeEvent(final PropertyChangeEvent event) {
379         for (final PropertyChangeListener listener : propertyChangeListeners) {
380             listener.propertyChange(event);
381         }
382     }
383 
384     public void addPropertyChangeListener(final PropertyChangeListener listener) {
385         propertyChangeListeners.add(Assert.requireNonNull(listener, "listener"));
386     }
387 
388     public void removePropertyChangeListener(final PropertyChangeListener listener) {
389         propertyChangeListeners.remove(listener);
390     }
391 
392     /**
393      * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
394      * current configuration. Use
395      * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link
396      * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration.
397      * @return the initial configuration location or {@code null}
398      */
399     public synchronized URI getConfigLocation() {
400         return configLocation;
401     }
402 
403     /**
404      * Sets the configLocation to the specified value and reconfigures this context.
405      * @param configLocation the location of the new configuration
406      */
407     public synchronized void setConfigLocation(final URI configLocation) {
408         this.configLocation = configLocation;
409         reconfigure();
410     }
411 
412     /**
413      * Reconfigure the context.
414      */
415     public synchronized void reconfigure() {
416         final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
417         LOGGER.debug("Reconfiguration started for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
418             configLocation, this, cl);
419         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation, cl);
420         setConfiguration(instance);
421         /*
422          * instance.start(); Configuration old = setConfiguration(instance);
423          * updateLoggers(); if (old != null) { old.stop(); }
424          */
425 
426         LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
427             configLocation, this, cl);
428     }
429 
430     /**
431      * Cause all Loggers to be updated against the current Configuration.
432      */
433     public void updateLoggers() {
434         updateLoggers(this.config);
435     }
436 
437     /**
438      * Cause all Logger to be updated against the specified Configuration.
439      * @param config The Configuration.
440      */
441     public void updateLoggers(final Configuration config) {
442         for (final Logger logger : loggers.values()) {
443             logger.updateConfiguration(config);
444         }
445     }
446 
447     /**
448      * Cause a reconfiguration to take place when the underlying configuration
449      * file changes.
450      *
451      * @param reconfigurable The Configuration that can be reconfigured.
452      */
453     @Override
454     public synchronized void onChange(final Reconfigurable reconfigurable) {
455         LOGGER.debug("Reconfiguration started for context {} ({})", name, this);
456         final Configuration config = reconfigurable.reconfigure();
457         if (config != null) {
458             setConfiguration(config);
459             LOGGER.debug("Reconfiguration completed for {} ({})", name, this);
460         } else {
461             LOGGER.debug("Reconfiguration failed for {} ({})", name, this);
462         }
463     }
464 
465     // LOG4J2-151: changed visibility from private to protected
466     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
467         return new Logger(ctx, name, messageFactory);
468     }
469 
470 }