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.concurrent.ConcurrentHashMap;
24  import java.util.concurrent.ConcurrentMap;
25  import java.util.concurrent.CopyOnWriteArrayList;
26  import java.util.concurrent.locks.Lock;
27  import java.util.concurrent.locks.ReentrantLock;
28  
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.core.config.Configuration;
31  import org.apache.logging.log4j.core.config.ConfigurationFactory;
32  import org.apache.logging.log4j.core.config.ConfigurationListener;
33  import org.apache.logging.log4j.core.config.DefaultConfiguration;
34  import org.apache.logging.log4j.core.config.NullConfiguration;
35  import org.apache.logging.log4j.core.config.Reconfigurable;
36  import org.apache.logging.log4j.core.helpers.Assert;
37  import org.apache.logging.log4j.core.helpers.NetUtils;
38  import org.apache.logging.log4j.core.jmx.Server;
39  import org.apache.logging.log4j.message.MessageFactory;
40  import org.apache.logging.log4j.spi.AbstractLogger;
41  import org.apache.logging.log4j.status.StatusLogger;
42  
43  /**
44   * The LoggerContext is the anchor for the logging system. It maintains a list
45   * of all the loggers requested by applications and a reference to the
46   * Configuration. The Configuration will contain the configured loggers,
47   * appenders, filters, etc and will be atomically updated whenever a reconfigure
48   * occurs.
49   */
50  public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle {
51  
52      public static final String PROPERTY_CONFIG = "config";
53      private static final StatusLogger LOGGER = StatusLogger.getLogger();
54      private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
55  
56      private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
57      private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
58  
59      /**
60       * The Configuration is volatile to guarantee that initialization of the
61       * Configuration has completed before the reference is updated.
62       */
63      private volatile Configuration config = new DefaultConfiguration();
64      private Object externalContext;
65      private final String name;
66      private URI configLocation;
67  
68      private ShutdownThread shutdownThread = null;
69  
70      /**
71       * Status of the LoggerContext.
72       */
73      public enum Status {
74          /** Initialized but not yet started. */
75          INITIALIZED,
76          /** In the process of starting. */
77          STARTING,
78          /** Is active. */
79          STARTED,
80          /** Shutdown is in progress. */
81          STOPPING,
82          /** Has shutdown. */
83          STOPPED
84      }
85  
86      private volatile Status status = Status.INITIALIZED;
87  
88      private final Lock configLock = new ReentrantLock();
89  
90      /**
91       * Constructor taking only a name.
92       * @param name The context name.
93       */
94      public LoggerContext(final String name) {
95          this(name, null, (URI) null);
96      }
97  
98      /**
99       * Constructor taking a name and a reference to an external context.
100      * @param name The context name.
101      * @param externalContext The external context.
102      */
103     public LoggerContext(final String name, final Object externalContext) {
104         this(name, externalContext, (URI) null);
105     }
106 
107     /**
108      * Constructor taking a name, external context and a configuration URI.
109      * @param name The context name.
110      * @param externalContext The external context.
111      * @param configLocn The location of the configuration as a URI.
112      */
113     public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
114         this.name = name;
115         this.externalContext = externalContext;
116         this.configLocation = configLocn;
117     }
118 
119     /**
120      * Constructor taking a name external context and a configuration location
121      * String. The location must be resolvable to a File.
122      *
123      * @param name The configuration location.
124      * @param externalContext The external context.
125      * @param configLocn The configuration location.
126      */
127     public LoggerContext(final String name, final Object externalContext, final String configLocn) {
128         this.name = name;
129         this.externalContext = externalContext;
130         if (configLocn != null) {
131             URI uri;
132             try {
133                 uri = new File(configLocn).toURI();
134             } catch (final Exception ex) {
135                 uri = null;
136             }
137             configLocation = uri;
138         } else {
139             configLocation = null;
140         }
141     }
142 
143     @Override
144     public void start() {
145         if (configLock.tryLock()) {
146             try {
147                 if (status == Status.INITIALIZED || status == Status.STOPPED) {
148                     status = Status.STARTING;
149                     reconfigure();
150                     if (config.isShutdownHookEnabled()) {
151                         shutdownThread = new ShutdownThread(this);
152                         try {
153                             Runtime.getRuntime().addShutdownHook(shutdownThread);
154                         } catch (final IllegalStateException ise) {
155                             LOGGER.warn("Unable to register shutdown hook due to JVM state");
156                             shutdownThread = null;
157                         } catch (final SecurityException se) {
158                             LOGGER.warn("Unable to register shutdown hook due to security restrictions");
159                             shutdownThread = null;
160                         }
161                     }
162                     status = Status.STARTED;
163                 }
164             } finally {
165                 configLock.unlock();
166             }
167         }
168     }
169 
170     /**
171      * Start with a specific configuration.
172      * @param config The new Configuration.
173      */
174     public void start(final Configuration config) {
175         if (configLock.tryLock()) {
176             try {
177                 if ((status == Status.INITIALIZED || status == Status.STOPPED) && config.isShutdownHookEnabled() ) {
178                     shutdownThread = new ShutdownThread(this);
179                     try {
180                         Runtime.getRuntime().addShutdownHook(shutdownThread);
181                     } catch (final IllegalStateException ise) {
182                         LOGGER.warn("Unable to register shutdown hook due to JVM state");
183                         shutdownThread = null;
184                     } catch (final SecurityException se) {
185                         LOGGER.warn("Unable to register shutdown hook due to security restrictions");
186                         shutdownThread = null;
187                     }
188                     status = Status.STARTED;
189                 }
190             } finally {
191                 configLock.unlock();
192             }
193         }
194         setConfiguration(config);
195     }
196 
197     @Override
198     public void stop() {
199         configLock.lock();
200         try {
201             if (status == Status.STOPPED) {
202                 return;
203             }
204             status = Status.STOPPING;
205             if (shutdownThread != null) {
206                 Runtime.getRuntime().removeShutdownHook(shutdownThread);
207                 shutdownThread = null;
208             }
209             final Configuration prev = config;
210             config = NULL_CONFIGURATION;
211             updateLoggers();
212             prev.stop();
213             externalContext = null;
214             LogManager.getFactory().removeContext(this);
215             status = Status.STOPPED;
216         } finally {
217             configLock.unlock();
218             
219             // in finally: unregister MBeans even if an exception occurred while stopping 
220             Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
221         }
222     }
223 
224     /**
225      * Gets the name.
226      *
227      * @return the name.
228      */
229     public String getName() {
230         return name;
231     }
232 
233     public Status getStatus() {
234         return status;
235     }
236 
237     @Override
238     public boolean isStarted() {
239         return status == Status.STARTED;
240     }
241 
242     /**
243      * Set the external context.
244      * @param context The external context.
245      */
246     public void setExternalContext(final Object context) {
247         this.externalContext = context;
248     }
249 
250     /**
251      * Returns the external context.
252      * @return The external context.
253      */
254     @Override
255     public Object getExternalContext() {
256         return this.externalContext;
257     }
258 
259     /**
260      * Obtain a Logger from the Context.
261      * @param name The name of the Logger to return.
262      * @return The Logger.
263      */
264     @Override
265     public Logger getLogger(final String name) {
266         return getLogger(name, null);
267     }
268 
269     /**
270      * Obtain a Logger from the Context.
271      * @param name The name of the Logger to return.
272      * @param messageFactory The message factory is used only when creating a
273      *            logger, subsequent use does not change the logger but will log
274      *            a warning if mismatched.
275      * @return The Logger.
276      */
277     @Override
278     public Logger getLogger(final String name, final MessageFactory messageFactory) {
279         Logger logger = loggers.get(name);
280         if (logger != null) {
281             AbstractLogger.checkMessageFactory(logger, messageFactory);
282             return logger;
283         }
284 
285         logger = newInstance(this, name, messageFactory);
286         final Logger prev = loggers.putIfAbsent(name, logger);
287         return prev == null ? logger : prev;
288     }
289 
290     /**
291      * Determine if the specified Logger exists.
292      * @param name The Logger name to search for.
293      * @return True if the Logger exists, false otherwise.
294      */
295     @Override
296     public boolean hasLogger(final String name) {
297         return loggers.containsKey(name);
298     }
299 
300     /**
301      * Returns the current Configuration. The Configuration will be replaced
302      * when a reconfigure occurs.
303      *
304      * @return The Configuration.
305      */
306     public Configuration getConfiguration() {
307         return config;
308     }
309 
310     /**
311      * Add a Filter to the Configuration. Filters that are added through the API will be lost
312      * when a reconfigure occurs.
313      * @param filter The Filter to add.
314      */
315     public void addFilter(final Filter filter) {
316         config.addFilter(filter);
317     }
318 
319     /**
320      * Removes a Filter from the current Configuration.
321      * @param filter The Filter to remove.
322      */
323     public void removeFilter(final Filter filter) {
324         config.removeFilter(filter);
325     }
326 
327     /**
328      * Set the Configuration to be used.
329      * @param config The new Configuration.
330      * @return The previous Configuration.
331      */
332     private synchronized Configuration setConfiguration(final Configuration config) {
333         if (config == null) {
334             throw new NullPointerException("No Configuration was provided");
335         }
336         final Configuration prev = this.config;
337         config.addListener(this);
338         final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
339         map.putIfAbsent("hostName", NetUtils.getLocalHostname());
340         map.putIfAbsent("contextName", name);
341         config.start();
342         this.config = config;
343         updateLoggers();
344         if (prev != null) {
345             prev.removeListener(this);
346             prev.stop();
347         }
348 
349         // notify listeners
350         final PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config);
351         for (final PropertyChangeListener listener : propertyChangeListeners) {
352             listener.propertyChange(evt);
353         }
354 
355         try {
356             Server.reregisterMBeansAfterReconfigure();
357         } catch (final Exception ex) {
358             LOGGER.error("Could not reconfigure JMX", ex);
359         }
360         return prev;
361     }
362 
363     public void addPropertyChangeListener(final PropertyChangeListener listener) {
364         propertyChangeListeners.add(Assert.isNotNull(listener, "listener"));
365     }
366 
367     public void removePropertyChangeListener(final PropertyChangeListener listener) {
368         propertyChangeListeners.remove(listener);
369     }
370 
371     public synchronized URI getConfigLocation() {
372         return configLocation;
373     }
374 
375     public synchronized void setConfigLocation(final URI configLocation) {
376         this.configLocation = configLocation;
377         reconfigure();
378     }
379 
380     /**
381      * Reconfigure the context.
382      */
383     public synchronized void reconfigure() {
384         LOGGER.debug("Reconfiguration started for context " + name);
385         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
386         setConfiguration(instance);
387         /*
388          * instance.start(); Configuration old = setConfiguration(instance);
389          * updateLoggers(); if (old != null) { old.stop(); }
390          */
391 
392         LOGGER.debug("Reconfiguration completed");
393     }
394 
395     /**
396      * Cause all Loggers to be updated against the current Configuration.
397      */
398     public void updateLoggers() {
399         updateLoggers(this.config);
400     }
401 
402     /**
403      * Cause all Logger to be updated against the specified Configuration.
404      * @param config The Configuration.
405      */
406     public void updateLoggers(final Configuration config) {
407         for (final Logger logger : loggers.values()) {
408             logger.updateConfiguration(config);
409         }
410     }
411 
412     /**
413      * Cause a reconfiguration to take place when the underlying configuration
414      * file changes.
415      *
416      * @param reconfigurable The Configuration that can be reconfigured.
417      */
418     @Override
419     public synchronized void onChange(final Reconfigurable reconfigurable) {
420         LOGGER.debug("Reconfiguration started for context " + name);
421         final Configuration config = reconfigurable.reconfigure();
422         if (config != null) {
423             setConfiguration(config);
424             LOGGER.debug("Reconfiguration completed");
425         } else {
426             LOGGER.debug("Reconfiguration failed");
427         }
428     }
429 
430     // LOG4J2-151: changed visibility from private to protected
431     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
432         return new Logger(ctx, name, messageFactory);
433     }
434 
435     private class ShutdownThread extends Thread {
436 
437         private final LoggerContext context;
438 
439         public ShutdownThread(final LoggerContext context) {
440             this.context = context;
441         }
442 
443         @Override
444         public void run() {
445             context.shutdownThread = null;
446             context.stop();
447         }
448     }
449 
450 }