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                     shutdownThread = new ShutdownThread(this);
151                     try {
152                         Runtime.getRuntime().addShutdownHook(shutdownThread);
153                     } catch (SecurityException se) {
154                         LOGGER.warn("Unable to register shutdown hook due to security restrictions");
155                         shutdownThread = null;
156                     }
157                     status = Status.STARTED;
158                 }
159             } finally {
160                 configLock.unlock();
161             }
162         }
163     }
164 
165     /**
166      * Start with a specific configuration.
167      * @param config The new Configuration.
168      */
169     public void start(final Configuration config) {
170         if (configLock.tryLock()) {
171             try {
172                 if (status == Status.INITIALIZED || status == Status.STOPPED) {
173                     shutdownThread = new ShutdownThread(this);
174                     try {
175                         Runtime.getRuntime().addShutdownHook(shutdownThread);
176                     } catch (SecurityException se) {
177                         LOGGER.warn("Unable to register shutdown hook due to security restrictions");
178                         shutdownThread = null;
179                     }
180                     status = Status.STARTED;
181                 }
182             } finally {
183                 configLock.unlock();
184             }
185         }
186         setConfiguration(config);
187     }
188 
189     @Override
190     public void stop() {
191         configLock.lock();
192         try {
193             if (status == Status.STOPPED) {
194                 return;
195             }
196             status = Status.STOPPING;
197             if (shutdownThread != null) {
198                 Runtime.getRuntime().removeShutdownHook(shutdownThread);
199                 shutdownThread = null;
200             }
201             Configuration prev = config;
202             config = new NullConfiguration();
203             updateLoggers();
204             prev.stop();
205             externalContext = null;
206             LogManager.getFactory().removeContext(this);
207             status = Status.STOPPED;
208         } finally {
209             configLock.unlock();
210         }
211     }
212 
213     /**
214      * Gets the name.
215      *
216      * @return the name.
217      */
218     public String getName() {
219         return name;
220     }
221 
222     public Status getStatus() {
223         return status;
224     }
225 
226     @Override
227     public boolean isStarted() {
228         return status == Status.STARTED;
229     }
230 
231     /**
232      * Set the external context.
233      * @param context The external context.
234      */
235     public void setExternalContext(final Object context) {
236         this.externalContext = context;
237     }
238 
239     /**
240      * Returns the external context.
241      * @return The external context.
242      */
243     @Override
244     public Object getExternalContext() {
245         return this.externalContext;
246     }
247 
248     /**
249      * Obtain a Logger from the Context.
250      * @param name The name of the Logger to return.
251      * @return The Logger.
252      */
253     @Override
254     public Logger getLogger(final String name) {
255         return getLogger(name, null);
256     }
257 
258     /**
259      * Obtain a Logger from the Context.
260      * @param name The name of the Logger to return.
261      * @param messageFactory The message factory is used only when creating a
262      *            logger, subsequent use does not change the logger but will log
263      *            a warning if mismatched.
264      * @return The Logger.
265      */
266     @Override
267     public Logger getLogger(final String name, final MessageFactory messageFactory) {
268         Logger logger = loggers.get(name);
269         if (logger != null) {
270             AbstractLogger.checkMessageFactory(logger, messageFactory);
271             return logger;
272         }
273 
274         logger = newInstance(this, name, messageFactory);
275         final Logger prev = loggers.putIfAbsent(name, logger);
276         return prev == null ? logger : prev;
277     }
278 
279     /**
280      * Determine if the specified Logger exists.
281      * @param name The Logger name to search for.
282      * @return True if the Logger exists, false otherwise.
283      */
284     @Override
285     public boolean hasLogger(final String name) {
286         return loggers.containsKey(name);
287     }
288 
289     /**
290      * Returns the current Configuration. The Configuration will be replaced
291      * when a reconfigure occurs.
292      *
293      * @return The Configuration.
294      */
295     public Configuration getConfiguration() {
296         return config;
297     }
298 
299     /**
300      * Add a Filter to the Configuration. Filters that are added through the API will be lost
301      * when a reconfigure occurs.
302      * @param filter The Filter to add.
303      */
304     public void addFilter(final Filter filter) {
305         config.addFilter(filter);
306     }
307 
308     /**
309      * Removes a Filter from the current Configuration.
310      * @param filter The Filter to remove.
311      */
312     public void removeFilter(final Filter filter) {
313         config.removeFilter(filter);
314     }
315 
316     /**
317      * Set the Configuration to be used.
318      * @param config The new Configuration.
319      * @return The previous Configuration.
320      */
321     private synchronized Configuration setConfiguration(final Configuration config) {
322         if (config == null) {
323             throw new NullPointerException("No Configuration was provided");
324         }
325         final Configuration prev = this.config;
326         config.addListener(this);
327         final Map<String, String> map = new HashMap<String, String>();
328         map.put("hostName", NetUtils.getLocalHostname());
329         map.put("contextName", name);
330         config.addComponent(Configuration.CONTEXT_PROPERTIES, map);
331         config.start();
332         this.config = config;
333         updateLoggers();
334         if (prev != null) {
335             prev.removeListener(this);
336             prev.stop();
337         }
338 
339         // notify listeners
340         PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config);
341         for (PropertyChangeListener listener : propertyChangeListeners) {
342             listener.propertyChange(evt);
343         }
344         return prev;
345     }
346 
347     public void addPropertyChangeListener(PropertyChangeListener listener) {
348         propertyChangeListeners.add(Assert.isNotNull(listener, "listener"));
349     }
350 
351     public void removePropertyChangeListener(PropertyChangeListener listener) {
352         propertyChangeListeners.remove(listener);
353     }
354 
355     public synchronized URI getConfigLocation() {
356         return configLocation;
357     }
358 
359     public synchronized void setConfigLocation(URI configLocation) {
360         this.configLocation = configLocation;
361         reconfigure();
362     }
363 
364     /**
365      * Reconfigure the context.
366      */
367     public synchronized void reconfigure() {
368         LOGGER.debug("Reconfiguration started for context " + name);
369         final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation);
370         setConfiguration(instance);
371         /*
372          * instance.start(); Configuration old = setConfiguration(instance);
373          * updateLoggers(); if (old != null) { old.stop(); }
374          */
375         LOGGER.debug("Reconfiguration completed");
376     }
377 
378     /**
379      * Cause all Loggers to be updated against the current Configuration.
380      */
381     public void updateLoggers() {
382         updateLoggers(this.config);
383     }
384 
385     /**
386      * Cause all Logger to be updated against the specified Configuration.
387      * @param config The Configuration.
388      */
389     public void updateLoggers(final Configuration config) {
390         for (final Logger logger : loggers.values()) {
391             logger.updateConfiguration(config);
392         }
393     }
394 
395     /**
396      * Cause a reconfiguration to take place when the underlying configuration
397      * file changes.
398      *
399      * @param reconfigurable The Configuration that can be reconfigured.
400      */
401     @Override
402     public synchronized void onChange(final Reconfigurable reconfigurable) {
403         LOGGER.debug("Reconfiguration started for context " + name);
404         final Configuration config = reconfigurable.reconfigure();
405         if (config != null) {
406             setConfiguration(config);
407             LOGGER.debug("Reconfiguration completed");
408         } else {
409             LOGGER.debug("Reconfiguration failed");
410         }
411     }
412 
413     // LOG4J2-151: changed visibility from private to protected
414     protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
415         return new Logger(ctx, name, messageFactory);
416     }
417 
418     private class ShutdownThread extends Thread {
419 
420         private final LoggerContext context;
421 
422         public ShutdownThread(LoggerContext context) {
423             this.context = context;
424         }
425 
426         @Override
427         public void run() {
428             context.shutdownThread = null;
429             context.stop();
430         }
431     }
432 
433 }