001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.core;
018
019 import java.beans.PropertyChangeEvent;
020 import java.beans.PropertyChangeListener;
021 import java.io.File;
022 import java.net.URI;
023 import java.util.HashMap;
024 import java.util.Map;
025 import java.util.concurrent.ConcurrentHashMap;
026 import java.util.concurrent.ConcurrentMap;
027 import java.util.concurrent.CopyOnWriteArrayList;
028 import java.util.concurrent.locks.Lock;
029 import java.util.concurrent.locks.ReentrantLock;
030
031 import org.apache.logging.log4j.core.config.Configuration;
032 import org.apache.logging.log4j.core.config.ConfigurationFactory;
033 import org.apache.logging.log4j.core.config.ConfigurationListener;
034 import org.apache.logging.log4j.core.config.DefaultConfiguration;
035 import org.apache.logging.log4j.core.config.NullConfiguration;
036 import org.apache.logging.log4j.core.config.Reconfigurable;
037 import org.apache.logging.log4j.core.helpers.Assert;
038 import org.apache.logging.log4j.core.helpers.NetUtils;
039 import org.apache.logging.log4j.message.MessageFactory;
040 import org.apache.logging.log4j.spi.AbstractLogger;
041 import org.apache.logging.log4j.status.StatusLogger;
042
043 /**
044 * The LoggerContext is the anchor for the logging system. It maintains a list
045 * of all the loggers requested by applications and a reference to the
046 * Configuration. The Configuration will contain the configured loggers,
047 * appenders, filters, etc and will be atomically updated whenever a reconfigure
048 * occurs.
049 */
050 public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle {
051
052 public static final String PROPERTY_CONFIG = "config";
053 private static final StatusLogger LOGGER = StatusLogger.getLogger();
054
055 private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
056 private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
057
058 /**
059 * The Configuration is volatile to guarantee that initialization of the
060 * Configuration has completed before the reference is updated.
061 */
062 private volatile Configuration config = new DefaultConfiguration();
063 private Object externalContext;
064 private final String name;
065 private URI configLocation;
066
067 private ShutdownThread shutdownThread = null;
068
069 /**
070 * Status of the LoggerContext.
071 */
072 public enum Status {
073 /** Initialized but not yet started. */
074 INITIALIZED,
075 /** In the process of starting. */
076 STARTING,
077 /** Is active. */
078 STARTED,
079 /** Shutdown is in progress. */
080 STOPPING,
081 /** Has shutdown. */
082 STOPPED
083 }
084
085 private volatile Status status = Status.INITIALIZED;
086
087 private final Lock configLock = new ReentrantLock();
088
089 /**
090 * Constructor taking only a name.
091 * @param name The context name.
092 */
093 public LoggerContext(final String name) {
094 this(name, null, (URI) null);
095 }
096
097 /**
098 * Constructor taking a name and a reference to an external context.
099 * @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 }