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.HashMap;
24  import java.util.Map;
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.config.Configuration;
33  import org.apache.logging.log4j.core.config.ConfigurationFactory;
34  import org.apache.logging.log4j.core.config.ConfigurationListener;
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.helpers.Assert;
39  import org.apache.logging.log4j.core.helpers.NetUtils;
40  import org.apache.logging.log4j.message.MessageFactory;
41  import org.apache.logging.log4j.spi.AbstractLogger;
42  import org.apache.logging.log4j.status.StatusLogger;
43  
44  /**
45   * The LoggerContext is the anchor for the logging system. It maintains a list
46   * of all the loggers requested by applications and a reference to the
47   * Configuration. The Configuration will contain the configured loggers,
48   * appenders, filters, etc and will be atomically updated whenever a reconfigure
49   * occurs.
50   */
51  public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle {
52  
53      public static final String PROPERTY_CONFIG = "config";
54      private static final StatusLogger LOGGER = StatusLogger.getLogger();
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 = new NullConfiguration();
211             updateLoggers();
212             prev.stop();
213             externalContext = null;
214             LogManager.getFactory().removeContext(this);
215             status = Status.STOPPED;
216         } finally {
217             configLock.unlock();
218         }
219     }
220 
221     /**
222      * Gets the name.
223      *
224      * @return the name.
225      */
226     public String getName() {
227         return name;
228     }
229 
230     public Status getStatus() {
231         return status;
232     }
233 
234     @Override
235     public boolean isStarted() {
236         return status == Status.STARTED;
237     }
238 
239     /**
240      * Set the external context.
241      * @param context The external context.
242      */
243     public void setExternalContext(final Object context) {
244         this.externalContext = context;
245     }
246 
247     /**
248      * Returns the external context.
249      * @return The external context.
250      */
251     @Override
252     public Object getExternalContext() {
253         return this.externalContext;
254     }
255 
256     /**
257      * Obtain a Logger from the Context.
258      * @param name The name of the Logger to return.
259      * @return The Logger.
260      */
261     @Override
262     public Logger getLogger(final String name) {
263         return getLogger(name, null);
264     }
265 
266     /**
267      * Obtain a Logger from the Context.
268      * @param name The name of the Logger to return.
269      * @param messageFactory The message factory is used only when creating a
270      *            logger, subsequent use does not change the logger but will log
271      *            a warning if mismatched.
272      * @return The Logger.
273      */
274     @Override
275     public Logger getLogger(final String name, final MessageFactory messageFactory) {
276         Logger logger = loggers.get(name);
277         if (logger != null) {
278             AbstractLogger.checkMessageFactory(logger, messageFactory);
279             return logger;
280         }
281 
282         logger = newInstance(this, name, messageFactory);
283         final Logger prev = loggers.putIfAbsent(name, logger);
284         return prev == null ? logger : prev;
285     }
286 
287     /**
288      * Determine if the specified Logger exists.
289      * @param name The Logger name to search for.
290      * @return True if the Logger exists, false otherwise.
291      */
292     @Override
293     public boolean hasLogger(final String name) {
294         return loggers.containsKey(name);
295     }
296 
297     /**
298      * Returns the current Configuration. The Configuration will be replaced
299      * when a reconfigure occurs.
300      *
301      * @return The Configuration.
302      */
303     public Configuration getConfiguration() {
304         return config;
305     }
306 
307     /**
308      * Add a Filter to the Configuration. Filters that are added through the API will be lost
309      * when a reconfigure occurs.
310      * @param filter The Filter to add.
311      */
312     public void addFilter(final Filter filter) {
313         config.addFilter(filter);
314     }
315 
316     /**
317      * Removes a Filter from the current Configuration.
318      * @param filter The Filter to remove.
319      */
320     public void removeFilter(final Filter filter) {
321         config.removeFilter(filter);
322     }
323 
324     /**
325      * Set the Configuration to be used.
326      * @param config The new Configuration.
327      * @return The previous Configuration.
328      */
329     private synchronized Configuration setConfiguration(final Configuration config) {
330         if (config == null) {
331             throw new NullPointerException("No Configuration was provided");
332         }
333         final Configuration prev = this.config;
334         config.addListener(this);
335         final Map<String, String> map = new HashMap<String, String>();
336         map.put("hostName", NetUtils.getLocalHostname());
337         map.put("contextName", name);
338         config.addComponent(Configuration.CONTEXT_PROPERTIES, map);
339         config.start();
340         this.config = config;
341         updateLoggers();
342         if (prev != null) {
343             prev.removeListener(this);
344             prev.stop();
345         }
346 
347         // notify listeners
348         final PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config);
349         for (final PropertyChangeListener listener : propertyChangeListeners) {
350             listener.propertyChange(evt);
351         }
352         return prev;
353     }
354 
355     public void addPropertyChangeListener(final PropertyChangeListener listener) {
356         propertyChangeListeners.add(Assert.isNotNull(listener, "listener"));
357     }
358 
359     public void removePropertyChangeListener(final PropertyChangeListener listener) {
360         propertyChangeListeners.remove(listener);
361     }
362 
363     public synchronized URI getConfigLocation() {
364         return configLocation;
365     }
366 
367     public synchronized void setConfigLocation(final URI configLocation) {
368         this.configLocation = configLocation;
369         reconfigure();
370     }
371 
372     /**
373      * Reconfigure the context.
374      */
375     public synchronized void reconfigure() {
376         LOGGER.debug("Reconfiguration started for context " + name);
377         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
378         setConfiguration(instance);
379         /*
380          * instance.start(); Configuration old = setConfiguration(instance);
381          * updateLoggers(); if (old != null) { old.stop(); }
382          */
383         LOGGER.debug("Reconfiguration completed");
384     }
385 
386     /**
387      * Cause all Loggers to be updated against the current Configuration.
388      */
389     public void updateLoggers() {
390         updateLoggers(this.config);
391     }
392 
393     /**
394      * Cause all Logger to be updated against the specified Configuration.
395      * @param config The Configuration.
396      */
397     public void updateLoggers(final Configuration config) {
398         for (final Logger logger : loggers.values()) {
399             logger.updateConfiguration(config);
400         }
401     }
402 
403     /**
404      * Cause a reconfiguration to take place when the underlying configuration
405      * file changes.
406      *
407      * @param reconfigurable The Configuration that can be reconfigured.
408      */
409     @Override
410     public synchronized void onChange(final Reconfigurable reconfigurable) {
411         LOGGER.debug("Reconfiguration started for context " + name);
412         final Configuration config = reconfigurable.reconfigure();
413         if (config != null) {
414             setConfiguration(config);
415             LOGGER.debug("Reconfiguration completed");
416         } else {
417             LOGGER.debug("Reconfiguration failed");
418         }
419     }
420 
421     // LOG4J2-151: changed visibility from private to protected
422     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
423         return new Logger(ctx, name, messageFactory);
424     }
425 
426     private class ShutdownThread extends Thread {
427 
428         private final LoggerContext context;
429 
430         public ShutdownThread(final LoggerContext context) {
431             this.context = context;
432         }
433 
434         @Override
435         public void run() {
436             context.shutdownThread = null;
437             context.stop();
438         }
439     }
440 
441 }