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