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