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.LogManager; 032 import org.apache.logging.log4j.core.config.Configuration; 033 import org.apache.logging.log4j.core.config.ConfigurationFactory; 034 import org.apache.logging.log4j.core.config.ConfigurationListener; 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.helpers.Assert; 039 import org.apache.logging.log4j.core.helpers.NetUtils; 040 import org.apache.logging.log4j.message.MessageFactory; 041 import org.apache.logging.log4j.spi.AbstractLogger; 042 import org.apache.logging.log4j.status.StatusLogger; 043 044 /** 045 * The LoggerContext is the anchor for the logging system. It maintains a list 046 * of all the loggers requested by applications and a reference to the 047 * Configuration. The Configuration will contain the configured loggers, 048 * appenders, filters, etc and will be atomically updated whenever a reconfigure 049 * occurs. 050 */ 051 public class LoggerContext implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener, LifeCycle { 052 053 public static final String PROPERTY_CONFIG = "config"; 054 private static final StatusLogger LOGGER = StatusLogger.getLogger(); 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 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 }