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 */ 017package org.apache.logging.log4j.core; 018 019import java.beans.PropertyChangeEvent; 020import java.beans.PropertyChangeListener; 021import java.io.File; 022import java.net.URI; 023import java.util.concurrent.ConcurrentHashMap; 024import java.util.concurrent.ConcurrentMap; 025import java.util.concurrent.CopyOnWriteArrayList; 026import java.util.concurrent.locks.Lock; 027import java.util.concurrent.locks.ReentrantLock; 028 029import org.apache.logging.log4j.LogManager; 030import org.apache.logging.log4j.core.config.Configuration; 031import org.apache.logging.log4j.core.config.ConfigurationFactory; 032import org.apache.logging.log4j.core.config.ConfigurationListener; 033import org.apache.logging.log4j.core.config.DefaultConfiguration; 034import org.apache.logging.log4j.core.config.NullConfiguration; 035import org.apache.logging.log4j.core.config.Reconfigurable; 036import org.apache.logging.log4j.core.helpers.Assert; 037import org.apache.logging.log4j.core.helpers.NetUtils; 038import org.apache.logging.log4j.core.jmx.Server; 039import org.apache.logging.log4j.message.MessageFactory; 040import org.apache.logging.log4j.spi.AbstractLogger; 041import 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 */ 050public 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 private static final Configuration NULL_CONFIGURATION = new NullConfiguration(); 055 056 private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<String, Logger>(); 057 private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>(); 058 059 /** 060 * The Configuration is volatile to guarantee that initialization of the 061 * Configuration has completed before the reference is updated. 062 */ 063 private volatile Configuration config = new DefaultConfiguration(); 064 private Object externalContext; 065 private final String name; 066 private URI configLocation; 067 068 private ShutdownThread shutdownThread = null; 069 070 /** 071 * Status of the LoggerContext. 072 */ 073 public enum Status { 074 /** Initialized but not yet started. */ 075 INITIALIZED, 076 /** In the process of starting. */ 077 STARTING, 078 /** Is active. */ 079 STARTED, 080 /** Shutdown is in progress. */ 081 STOPPING, 082 /** Has shutdown. */ 083 STOPPED 084 } 085 086 private volatile Status status = Status.INITIALIZED; 087 088 private final Lock configLock = new ReentrantLock(); 089 090 /** 091 * Constructor taking only a name. 092 * @param name The context name. 093 */ 094 public LoggerContext(final String name) { 095 this(name, null, (URI) null); 096 } 097 098 /** 099 * 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 if (config.isShutdownHookEnabled()) { 151 shutdownThread = new ShutdownThread(this); 152 try { 153 Runtime.getRuntime().addShutdownHook(shutdownThread); 154 } catch (final IllegalStateException ise) { 155 LOGGER.warn("Unable to register shutdown hook due to JVM state"); 156 shutdownThread = null; 157 } catch (final SecurityException se) { 158 LOGGER.warn("Unable to register shutdown hook due to security restrictions"); 159 shutdownThread = null; 160 } 161 } 162 status = Status.STARTED; 163 } 164 } finally { 165 configLock.unlock(); 166 } 167 } 168 } 169 170 /** 171 * Start with a specific configuration. 172 * @param config The new Configuration. 173 */ 174 public void start(final Configuration config) { 175 if (configLock.tryLock()) { 176 try { 177 if ((status == Status.INITIALIZED || status == Status.STOPPED) && config.isShutdownHookEnabled() ) { 178 shutdownThread = new ShutdownThread(this); 179 try { 180 Runtime.getRuntime().addShutdownHook(shutdownThread); 181 } catch (final IllegalStateException ise) { 182 LOGGER.warn("Unable to register shutdown hook due to JVM state"); 183 shutdownThread = null; 184 } catch (final SecurityException se) { 185 LOGGER.warn("Unable to register shutdown hook due to security restrictions"); 186 shutdownThread = null; 187 } 188 status = Status.STARTED; 189 } 190 } finally { 191 configLock.unlock(); 192 } 193 } 194 setConfiguration(config); 195 } 196 197 @Override 198 public void stop() { 199 configLock.lock(); 200 try { 201 if (status == Status.STOPPED) { 202 return; 203 } 204 status = Status.STOPPING; 205 if (shutdownThread != null) { 206 Runtime.getRuntime().removeShutdownHook(shutdownThread); 207 shutdownThread = null; 208 } 209 final Configuration prev = config; 210 config = NULL_CONFIGURATION; 211 updateLoggers(); 212 prev.stop(); 213 externalContext = null; 214 LogManager.getFactory().removeContext(this); 215 status = Status.STOPPED; 216 } finally { 217 configLock.unlock(); 218 219 // in finally: unregister MBeans even if an exception occurred while stopping 220 Server.unregisterLoggerContext(getName()); // LOG4J2-406, LOG4J2-500 221 } 222 } 223 224 /** 225 * Gets the name. 226 * 227 * @return the name. 228 */ 229 public String getName() { 230 return name; 231 } 232 233 public Status getStatus() { 234 return status; 235 } 236 237 @Override 238 public boolean isStarted() { 239 return status == Status.STARTED; 240 } 241 242 /** 243 * Set the external context. 244 * @param context The external context. 245 */ 246 public void setExternalContext(final Object context) { 247 this.externalContext = context; 248 } 249 250 /** 251 * Returns the external context. 252 * @return The external context. 253 */ 254 @Override 255 public Object getExternalContext() { 256 return this.externalContext; 257 } 258 259 /** 260 * Obtain a Logger from the Context. 261 * @param name The name of the Logger to return. 262 * @return The Logger. 263 */ 264 @Override 265 public Logger getLogger(final String name) { 266 return getLogger(name, null); 267 } 268 269 /** 270 * Obtain a Logger from the Context. 271 * @param name The name of the Logger to return. 272 * @param messageFactory The message factory is used only when creating a 273 * logger, subsequent use does not change the logger but will log 274 * a warning if mismatched. 275 * @return The Logger. 276 */ 277 @Override 278 public Logger getLogger(final String name, final MessageFactory messageFactory) { 279 Logger logger = loggers.get(name); 280 if (logger != null) { 281 AbstractLogger.checkMessageFactory(logger, messageFactory); 282 return logger; 283 } 284 285 logger = newInstance(this, name, messageFactory); 286 final Logger prev = loggers.putIfAbsent(name, logger); 287 return prev == null ? logger : prev; 288 } 289 290 /** 291 * Determine if the specified Logger exists. 292 * @param name The Logger name to search for. 293 * @return True if the Logger exists, false otherwise. 294 */ 295 @Override 296 public boolean hasLogger(final String name) { 297 return loggers.containsKey(name); 298 } 299 300 /** 301 * Returns the current Configuration. The Configuration will be replaced 302 * when a reconfigure occurs. 303 * 304 * @return The Configuration. 305 */ 306 public Configuration getConfiguration() { 307 return config; 308 } 309 310 /** 311 * Add a Filter to the Configuration. Filters that are added through the API will be lost 312 * when a reconfigure occurs. 313 * @param filter The Filter to add. 314 */ 315 public void addFilter(final Filter filter) { 316 config.addFilter(filter); 317 } 318 319 /** 320 * Removes a Filter from the current Configuration. 321 * @param filter The Filter to remove. 322 */ 323 public void removeFilter(final Filter filter) { 324 config.removeFilter(filter); 325 } 326 327 /** 328 * Set the Configuration to be used. 329 * @param config The new Configuration. 330 * @return The previous Configuration. 331 */ 332 private synchronized Configuration setConfiguration(final Configuration config) { 333 if (config == null) { 334 throw new NullPointerException("No Configuration was provided"); 335 } 336 final Configuration prev = this.config; 337 config.addListener(this); 338 final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES); 339 map.putIfAbsent("hostName", NetUtils.getLocalHostname()); 340 map.putIfAbsent("contextName", name); 341 config.start(); 342 this.config = config; 343 updateLoggers(); 344 if (prev != null) { 345 prev.removeListener(this); 346 prev.stop(); 347 } 348 349 // notify listeners 350 final PropertyChangeEvent evt = new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config); 351 for (final PropertyChangeListener listener : propertyChangeListeners) { 352 listener.propertyChange(evt); 353 } 354 355 try { 356 Server.reregisterMBeansAfterReconfigure(); 357 } catch (final Exception ex) { 358 LOGGER.error("Could not reconfigure JMX", ex); 359 } 360 return prev; 361 } 362 363 public void addPropertyChangeListener(final PropertyChangeListener listener) { 364 propertyChangeListeners.add(Assert.isNotNull(listener, "listener")); 365 } 366 367 public void removePropertyChangeListener(final PropertyChangeListener listener) { 368 propertyChangeListeners.remove(listener); 369 } 370 371 public synchronized URI getConfigLocation() { 372 return configLocation; 373 } 374 375 public synchronized void setConfigLocation(final URI configLocation) { 376 this.configLocation = configLocation; 377 reconfigure(); 378 } 379 380 /** 381 * Reconfigure the context. 382 */ 383 public synchronized void reconfigure() { 384 LOGGER.debug("Reconfiguration started for context " + name); 385 final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation); 386 setConfiguration(instance); 387 /* 388 * instance.start(); Configuration old = setConfiguration(instance); 389 * updateLoggers(); if (old != null) { old.stop(); } 390 */ 391 392 LOGGER.debug("Reconfiguration completed"); 393 } 394 395 /** 396 * Cause all Loggers to be updated against the current Configuration. 397 */ 398 public void updateLoggers() { 399 updateLoggers(this.config); 400 } 401 402 /** 403 * Cause all Logger to be updated against the specified Configuration. 404 * @param config The Configuration. 405 */ 406 public void updateLoggers(final Configuration config) { 407 for (final Logger logger : loggers.values()) { 408 logger.updateConfiguration(config); 409 } 410 } 411 412 /** 413 * Cause a reconfiguration to take place when the underlying configuration 414 * file changes. 415 * 416 * @param reconfigurable The Configuration that can be reconfigured. 417 */ 418 @Override 419 public synchronized void onChange(final Reconfigurable reconfigurable) { 420 LOGGER.debug("Reconfiguration started for context " + name); 421 final Configuration config = reconfigurable.reconfigure(); 422 if (config != null) { 423 setConfiguration(config); 424 LOGGER.debug("Reconfiguration completed"); 425 } else { 426 LOGGER.debug("Reconfiguration failed"); 427 } 428 } 429 430 // LOG4J2-151: changed visibility from private to protected 431 protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { 432 return new Logger(ctx, name, messageFactory); 433 } 434 435 private class ShutdownThread extends Thread { 436 437 private final LoggerContext context; 438 439 public ShutdownThread(final LoggerContext context) { 440 this.context = context; 441 } 442 443 @Override 444 public void run() { 445 context.shutdownThread = null; 446 context.stop(); 447 } 448 } 449 450}