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.NetUtils; 038 import org.apache.logging.log4j.core.jmx.Assert; 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) { 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 }