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.Collection;
024 import java.util.concurrent.ConcurrentHashMap;
025 import java.util.concurrent.ConcurrentMap;
026 import java.util.concurrent.CopyOnWriteArrayList;
027 import java.util.concurrent.locks.Lock;
028 import java.util.concurrent.locks.ReentrantLock;
029
030 import org.apache.logging.log4j.LogManager;
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.ConfigurationSource;
035 import org.apache.logging.log4j.core.config.DefaultConfiguration;
036 import org.apache.logging.log4j.core.config.NullConfiguration;
037 import org.apache.logging.log4j.core.config.Reconfigurable;
038 import org.apache.logging.log4j.core.jmx.Server;
039 import org.apache.logging.log4j.core.util.Assert;
040 import org.apache.logging.log4j.core.util.Cancellable;
041 import org.apache.logging.log4j.core.util.NetUtils;
042 import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
043 import org.apache.logging.log4j.message.MessageFactory;
044 import org.apache.logging.log4j.spi.AbstractLogger;
045 import org.apache.logging.log4j.spi.LoggerContextFactory;
046
047 import static org.apache.logging.log4j.core.util.ShutdownCallbackRegistry.SHUTDOWN_HOOK_MARKER;
048
049 /**
050 * The LoggerContext is the anchor for the logging system. It maintains a list
051 * of all the loggers requested by applications and a reference to the
052 * Configuration. The Configuration will contain the configured loggers,
053 * appenders, filters, etc and will be atomically updated whenever a reconfigure
054 * occurs.
055 */
056 public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener {
057
058 private static final long serialVersionUID = 1L;
059
060 public static final String PROPERTY_CONFIG = "config";
061 private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
062
063 private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>();
064 private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>();
065
066 /**
067 * The Configuration is volatile to guarantee that initialization of the
068 * Configuration has completed before the reference is updated.
069 */
070 private volatile Configuration config = new DefaultConfiguration();
071 private Object externalContext;
072 private final String name;
073 private volatile URI configLocation;
074 private Cancellable shutdownCallback;
075
076 private final Lock configLock = new ReentrantLock();
077
078 /**
079 * Constructor taking only a name.
080 * @param name The context name.
081 */
082 public LoggerContext(final String name) {
083 this(name, null, (URI) null);
084 }
085
086 /**
087 * Constructor taking a name and a reference to an external context.
088 * @param name The context name.
089 * @param externalContext The external context.
090 */
091 public LoggerContext(final String name, final Object externalContext) {
092 this(name, externalContext, (URI) null);
093 }
094
095 /**
096 * Constructor taking a name, external context and a configuration URI.
097 * @param name The context name.
098 * @param externalContext The external context.
099 * @param configLocn The location of the configuration as a URI.
100 */
101 public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
102 this.name = name;
103 this.externalContext = externalContext;
104 this.configLocation = configLocn;
105 }
106
107 /**
108 * Constructor taking a name external context and a configuration location
109 * String. The location must be resolvable to a File.
110 *
111 * @param name The configuration location.
112 * @param externalContext The external context.
113 * @param configLocn The configuration location.
114 */
115 public LoggerContext(final String name, final Object externalContext, final String configLocn) {
116 this.name = name;
117 this.externalContext = externalContext;
118 if (configLocn != null) {
119 URI uri;
120 try {
121 uri = new File(configLocn).toURI();
122 } catch (final Exception ex) {
123 uri = null;
124 }
125 configLocation = uri;
126 } else {
127 configLocation = null;
128 }
129 }
130
131 @Override
132 public void start() {
133 LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
134 if (configLock.tryLock()) {
135 try {
136 if (this.isInitialized() || this.isStopped()) {
137 this.setStarting();
138 reconfigure();
139 if (this.config.isShutdownHookEnabled()) {
140 setUpShutdownHook();
141 }
142 this.setStarted();
143 }
144 } finally {
145 configLock.unlock();
146 }
147 }
148 LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
149 }
150
151 /**
152 * Starts with a specific configuration.
153 * @param config The new Configuration.
154 */
155 public void start(final Configuration config) {
156 LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config);
157 if (configLock.tryLock()) {
158 try {
159 if (this.isInitialized() || this.isStopped()) {
160 if (this.config.isShutdownHookEnabled()) {
161 setUpShutdownHook();
162 }
163 this.setStarted();
164 }
165 } finally {
166 configLock.unlock();
167 }
168 }
169 setConfiguration(config);
170 LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config);
171 }
172
173 private void setUpShutdownHook() {
174 if (shutdownCallback == null) {
175 final LoggerContextFactory factory = LogManager.getFactory();
176 if (factory instanceof ShutdownCallbackRegistry) {
177 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Shutdown hook enabled. Registering a new one.");
178 try {
179 this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() {
180 @Override
181 public void run() {
182 final LoggerContext context = LoggerContext.this;
183 LOGGER.debug(SHUTDOWN_HOOK_MARKER, "Stopping LoggerContext[name={}, {}]", context.getName(),
184 context);
185 context.stop();
186 }
187
188 @Override
189 public String toString() {
190 return "Shutdown callback for LoggerContext[name=" + LoggerContext.this.getName() + ']';
191 }
192 });
193 } catch (final IllegalStateException ise) {
194 LOGGER.fatal(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook because JVM is shutting down.");
195 } catch (final SecurityException se) {
196 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Unable to register shutdown hook due to security restrictions");
197 }
198 }
199 }
200 }
201
202 @Override
203 public void stop() {
204 LOGGER.debug("Stopping LoggerContext[name={}, {}]...", getName(), this);
205 configLock.lock();
206 try {
207 if (this.isStopped()) {
208 return;
209 }
210
211 this.setStopping();
212 try {
213 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500
214 } catch (final Exception ex) {
215 LOGGER.error("Unable to unregister MBeans", ex);
216 }
217 if (shutdownCallback != null) {
218 shutdownCallback.cancel();
219 shutdownCallback = null;
220 }
221 final Configuration prev = config;
222 config = NULL_CONFIGURATION;
223 updateLoggers();
224 prev.stop();
225 externalContext = null;
226 LogManager.getFactory().removeContext(this);
227 this.setStopped();
228 } finally {
229 configLock.unlock();
230 }
231 LOGGER.debug("Stopped LoggerContext[name={}, {}]...", getName(), this);
232 }
233
234 /**
235 * Gets the name.
236 *
237 * @return the name.
238 */
239 public String getName() {
240 return name;
241 }
242
243 /**
244 * Sets the external context.
245 * @param context The external context.
246 */
247 public void setExternalContext(final Object context) {
248 this.externalContext = context;
249 }
250
251 /**
252 * Returns the external context.
253 * @return The external context.
254 */
255 @Override
256 public Object getExternalContext() {
257 return this.externalContext;
258 }
259
260 /**
261 * Obtains a Logger from the Context.
262 * @param name The name of the Logger to return.
263 * @return The Logger.
264 */
265 @Override
266 public Logger getLogger(final String name) {
267 return getLogger(name, null);
268 }
269
270 /**
271 * Gets a collection of the current loggers.
272 * <p>
273 * Whether this collection is a copy of the underlying collection or not is undefined. Therefore, modify this collection at your own
274 * risk.
275 * </p>
276 *
277 * @return a collection of the current loggers.
278 */
279 public Collection<Logger> getLoggers() {
280 return loggers.values();
281 }
282
283 /**
284 * Obtains a Logger from the Context.
285 * @param name The name of the Logger to return.
286 * @param messageFactory The message factory is used only when creating a
287 * logger, subsequent use does not change the logger but will log
288 * a warning if mismatched.
289 * @return The Logger.
290 */
291 @Override
292 public Logger getLogger(final String name, final MessageFactory messageFactory) {
293 Logger logger = loggers.get(name);
294 if (logger != null) {
295 AbstractLogger.checkMessageFactory(logger, messageFactory);
296 return logger;
297 }
298
299 logger = newInstance(this, name, messageFactory);
300 final Logger prev = loggers.putIfAbsent(name, logger);
301 return prev == null ? logger : prev;
302 }
303
304 /**
305 * Determines if the specified Logger exists.
306 * @param name The Logger name to search for.
307 * @return True if the Logger exists, false otherwise.
308 */
309 @Override
310 public boolean hasLogger(final String name) {
311 return loggers.containsKey(name);
312 }
313
314 /**
315 * Returns the current Configuration. The Configuration will be replaced
316 * when a reconfigure occurs.
317 *
318 * @return The Configuration.
319 */
320 public Configuration getConfiguration() {
321 return config;
322 }
323
324 /**
325 * Adds a Filter to the Configuration. Filters that are added through the API will be lost
326 * when a reconfigure occurs.
327 * @param filter The Filter to add.
328 */
329 public void addFilter(final Filter filter) {
330 config.addFilter(filter);
331 }
332
333 /**
334 * Removes a Filter from the current Configuration.
335 * @param filter The Filter to remove.
336 */
337 public void removeFilter(final Filter filter) {
338 config.removeFilter(filter);
339 }
340
341 /**
342 * Sets the Configuration to be used.
343 * @param config The new Configuration.
344 * @return The previous Configuration.
345 */
346 private Configuration setConfiguration(final Configuration config) {
347 Assert.requireNonNull(config, "No Configuration was provided");
348 configLock.lock();
349 try {
350 final Configuration prev = this.config;
351 config.addListener(this);
352 final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);
353
354 try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
355 map.putIfAbsent("hostName", NetUtils.getLocalHostname());
356 } catch (final Exception ex) {
357 LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
358 map.putIfAbsent("hostName", "unknown");
359 }
360 map.putIfAbsent("contextName", name);
361 config.start();
362 this.config = config;
363 updateLoggers();
364 if (prev != null) {
365 prev.removeListener(this);
366 prev.stop();
367 }
368
369 firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));
370
371 try {
372 Server.reregisterMBeansAfterReconfigure();
373 } catch (final Throwable t) {
374 // LOG4J2-716: Android has no java.lang.management
375 LOGGER.error("Could not reconfigure JMX", t);
376 }
377 return prev;
378 } finally {
379 configLock.unlock();
380 }
381 }
382
383 private void firePropertyChangeEvent(final PropertyChangeEvent event) {
384 for (final PropertyChangeListener listener : propertyChangeListeners) {
385 listener.propertyChange(event);
386 }
387 }
388
389 public void addPropertyChangeListener(final PropertyChangeListener listener) {
390 propertyChangeListeners.add(Assert.requireNonNull(listener, "listener"));
391 }
392
393 public void removePropertyChangeListener(final PropertyChangeListener listener) {
394 propertyChangeListeners.remove(listener);
395 }
396
397 /**
398 * Returns the initial configuration location or {@code null}. The returned value may not be the location of the
399 * current configuration. Use
400 * {@link #getConfiguration()}.{@link Configuration#getConfigurationSource() getConfigurationSource()}.{@link
401 * ConfigurationSource#getLocation() getLocation()} to get the actual source of the current configuration.
402 * @return the initial configuration location or {@code null}
403 */
404 public URI getConfigLocation() {
405 return configLocation;
406 }
407
408 /**
409 * Sets the configLocation to the specified value and reconfigures this context.
410 * @param configLocation the location of the new configuration
411 */
412 public void setConfigLocation(final URI configLocation) {
413 this.configLocation = configLocation;
414
415 reconfigure(configLocation);
416 }
417
418 /**
419 * Reconfigure the context.
420 */
421 private void reconfigure(final URI configURI) {
422 final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
423 LOGGER.debug("Reconfiguration started for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
424 configURI, this, cl);
425 final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configURI, cl);
426 setConfiguration(instance);
427 /*
428 * instance.start(); Configuration old = setConfiguration(instance);
429 * updateLoggers(); if (old != null) { old.stop(); }
430 */
431
432 LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
433 configURI, this, cl);
434 }
435
436 /**
437 * Reconfigure the context. Log4j does not remove Loggers during a reconfiguration. Log4j will create new
438 * LoggerConfig objects and Log4j will point the Loggers at the new LoggerConfigs. Log4j will free the old
439 * LoggerConfig, along with old Appenders and Filters.
440 */
441 public void reconfigure() {
442 reconfigure(configLocation);
443 }
444
445 /**
446 * Causes all Loggers to be updated against the current Configuration.
447 */
448 public void updateLoggers() {
449 updateLoggers(this.config);
450 }
451
452 /**
453 * Causes all Logger to be updated against the specified Configuration.
454 * @param config The Configuration.
455 */
456 public void updateLoggers(final Configuration config) {
457 for (final Logger logger : loggers.values()) {
458 logger.updateConfiguration(config);
459 }
460 }
461
462 /**
463 * Causes a reconfiguration to take place when the underlying configuration
464 * file changes.
465 *
466 * @param reconfigurable The Configuration that can be reconfigured.
467 */
468 @Override
469 public synchronized void onChange(final Reconfigurable reconfigurable) {
470 LOGGER.debug("Reconfiguration started for context {} ({})", name, this);
471 final Configuration newConfig = reconfigurable.reconfigure();
472 if (newConfig != null) {
473 setConfiguration(newConfig);
474 LOGGER.debug("Reconfiguration completed for {} ({})", name, this);
475 } else {
476 LOGGER.debug("Reconfiguration failed for {} ({})", name, this);
477 }
478 }
479
480 // LOG4J2-151: changed visibility from private to protected
481 protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) {
482 return new Logger(ctx, name, messageFactory);
483 }
484
485 }