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