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.impl; 018 019import java.net.URI; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.Map; 023import java.util.Objects; 024 025import org.apache.logging.log4j.core.LifeCycle; 026import org.apache.logging.log4j.core.LoggerContext; 027import org.apache.logging.log4j.core.config.AbstractConfiguration; 028import org.apache.logging.log4j.core.config.DefaultConfiguration; 029import org.apache.logging.log4j.core.config.composite.CompositeConfiguration; 030import org.apache.logging.log4j.core.config.Configuration; 031import org.apache.logging.log4j.core.config.ConfigurationFactory; 032import org.apache.logging.log4j.core.config.ConfigurationSource; 033import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector; 034import org.apache.logging.log4j.core.selector.ContextSelector; 035import org.apache.logging.log4j.core.util.Cancellable; 036import org.apache.logging.log4j.core.util.Constants; 037import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry; 038import org.apache.logging.log4j.core.util.Loader; 039import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; 040import org.apache.logging.log4j.spi.LoggerContextFactory; 041import org.apache.logging.log4j.status.StatusLogger; 042import org.apache.logging.log4j.util.PropertiesUtil; 043 044/** 045 * Factory to locate a ContextSelector and then load a LoggerContext. 046 */ 047public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallbackRegistry { 048 049 private static final StatusLogger LOGGER = StatusLogger.getLogger(); 050 private static final boolean SHUTDOWN_HOOK_ENABLED = 051 PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true) && 052 !Constants.IS_WEB_APP; 053 054 private final ContextSelector selector; 055 private final ShutdownCallbackRegistry shutdownCallbackRegistry; 056 057 /** 058 * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}. 059 */ 060 public Log4jContextFactory() { 061 this(createContextSelector(), createShutdownCallbackRegistry()); 062 } 063 064 /** 065 * Initializes this factory's ContextSelector with the specified selector. 066 * @param selector the selector to use 067 */ 068 public Log4jContextFactory(final ContextSelector selector) { 069 this(selector, createShutdownCallbackRegistry()); 070 } 071 072 /** 073 * Constructs a Log4jContextFactory using the ContextSelector from {@link Constants#LOG4J_CONTEXT_SELECTOR} 074 * and the provided ShutdownRegistrationStrategy. 075 * 076 * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use 077 * @since 2.1 078 */ 079 public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) { 080 this(createContextSelector(), shutdownCallbackRegistry); 081 } 082 083 /** 084 * Constructs a Log4jContextFactory using the provided ContextSelector and ShutdownRegistrationStrategy. 085 * 086 * @param selector the selector to use 087 * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use 088 * @since 2.1 089 */ 090 public Log4jContextFactory(final ContextSelector selector, 091 final ShutdownCallbackRegistry shutdownCallbackRegistry) { 092 this.selector = Objects.requireNonNull(selector, "No ContextSelector provided"); 093 this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided"); 094 LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass()); 095 initializeShutdownCallbackRegistry(); 096 } 097 098 private static ContextSelector createContextSelector() { 099 try { 100 final ContextSelector selector = Loader.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR, 101 ContextSelector.class); 102 if (selector != null) { 103 return selector; 104 } 105 } catch (final Exception e) { 106 LOGGER.error("Unable to create custom ContextSelector. Falling back to default.", e); 107 } 108 return new ClassLoaderContextSelector(); 109 } 110 111 private static ShutdownCallbackRegistry createShutdownCallbackRegistry() { 112 try { 113 final ShutdownCallbackRegistry registry = Loader.newCheckedInstanceOfProperty( 114 ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY, ShutdownCallbackRegistry.class 115 ); 116 if (registry != null) { 117 return registry; 118 } 119 } catch (final Exception e) { 120 LOGGER.error("Unable to create custom ShutdownCallbackRegistry. Falling back to default.", e); 121 } 122 return new DefaultShutdownCallbackRegistry(); 123 } 124 125 private void initializeShutdownCallbackRegistry() { 126 if (isShutdownHookEnabled() && this.shutdownCallbackRegistry instanceof LifeCycle) { 127 try { 128 ((LifeCycle) this.shutdownCallbackRegistry).start(); 129 } catch (final IllegalStateException e) { 130 LOGGER.error("Cannot start ShutdownCallbackRegistry, already shutting down."); 131 throw e; 132 } catch (final RuntimeException e) { 133 LOGGER.error("There was an error starting the ShutdownCallbackRegistry.", e); 134 } 135 } 136 } 137 138 /** 139 * Loads the LoggerContext using the ContextSelector. 140 * @param fqcn The fully qualified class name of the caller. 141 * @param loader The ClassLoader to use or null. 142 * @param currentContext If true returns the current Context, if false returns the Context appropriate 143 * for the caller if a more appropriate Context can be determined. 144 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 145 * @return The LoggerContext. 146 */ 147 @Override 148 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 149 final boolean currentContext) { 150 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext); 151 if (externalContext != null && ctx.getExternalContext() == null) { 152 ctx.setExternalContext(externalContext); 153 } 154 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 155 ctx.start(); 156 } 157 return ctx; 158 } 159 160 /** 161 * Loads the LoggerContext using the ContextSelector. 162 * @param fqcn The fully qualified class name of the caller. 163 * @param loader The ClassLoader to use or null. 164 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 165 * @param currentContext If true returns the current Context, if false returns the Context appropriate 166 * for the caller if a more appropriate Context can be determined. 167 * @param source The configuration source. 168 * @return The LoggerContext. 169 */ 170 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 171 final boolean currentContext, final ConfigurationSource source) { 172 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null); 173 if (externalContext != null && ctx.getExternalContext() == null) { 174 ctx.setExternalContext(externalContext); 175 } 176 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 177 if (source != null) { 178 ContextAnchor.THREAD_CONTEXT.set(ctx); 179 final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, source); 180 LOGGER.debug("Starting LoggerContext[name={}] from configuration {}", ctx.getName(), source); 181 ctx.start(config); 182 ContextAnchor.THREAD_CONTEXT.remove(); 183 } else { 184 ctx.start(); 185 } 186 } 187 return ctx; 188 } 189 190 /** 191 * Loads the LoggerContext using the ContextSelector using the provided Configuration 192 * @param fqcn The fully qualified class name of the caller. 193 * @param loader The ClassLoader to use or null. 194 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 195 * @param currentContext If true returns the current Context, if false returns the Context appropriate 196 * for the caller if a more appropriate Context can be determined. 197 * @param configuration The Configuration. 198 * @return The LoggerContext. 199 */ 200 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 201 final boolean currentContext, final Configuration configuration) { 202 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null); 203 if (externalContext != null && ctx.getExternalContext() == null) { 204 ctx.setExternalContext(externalContext); 205 } 206 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 207 ContextAnchor.THREAD_CONTEXT.set(ctx); 208 try { 209 ctx.start(configuration); 210 } finally { 211 ContextAnchor.THREAD_CONTEXT.remove(); 212 } 213 } 214 return ctx; 215 } 216 217 /** 218 * Loads the LoggerContext using the ContextSelector. 219 * @param fqcn The fully qualified class name of the caller. 220 * @param loader The ClassLoader to use or null. 221 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 222 * @param currentContext If true returns the current Context, if false returns the Context appropriate 223 * for the caller if a more appropriate Context can be determined. 224 * @param configLocation The location of the configuration for the LoggerContext (or null). 225 * @return The LoggerContext. 226 */ 227 @Override 228 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 229 final boolean currentContext, final URI configLocation, final String name) { 230 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, configLocation); 231 if (externalContext != null && ctx.getExternalContext() == null) { 232 ctx.setExternalContext(externalContext); 233 } 234 if (name != null) { 235 ctx.setName(name); 236 } 237 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 238 if (configLocation != null || name != null) { 239 ContextAnchor.THREAD_CONTEXT.set(ctx); 240 final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, name, configLocation); 241 LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation); 242 ctx.start(config); 243 ContextAnchor.THREAD_CONTEXT.remove(); 244 } else { 245 ctx.start(); 246 } 247 } 248 return ctx; 249 } 250 251 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Map.Entry<String, Object> entry, 252 final boolean currentContext, final URI configLocation, final String name) { 253 final LoggerContext ctx = selector.getContext(fqcn, loader, entry, currentContext, configLocation); 254 if (name != null) { 255 ctx.setName(name); 256 } 257 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 258 if (configLocation != null || name != null) { 259 ContextAnchor.THREAD_CONTEXT.set(ctx); 260 final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, name, configLocation); 261 LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation); 262 ctx.start(config); 263 ContextAnchor.THREAD_CONTEXT.remove(); 264 } else { 265 ctx.start(); 266 } 267 } 268 return ctx; 269 } 270 271 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 272 final boolean currentContext, final List<URI> configLocations, final String name) { 273 final LoggerContext ctx = selector 274 .getContext(fqcn, loader, currentContext, null/*this probably needs to change*/); 275 if (externalContext != null && ctx.getExternalContext() == null) { 276 ctx.setExternalContext(externalContext); 277 } 278 if (name != null) { 279 ctx.setName(name); 280 } 281 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 282 if ((configLocations != null && !configLocations.isEmpty())) { 283 ContextAnchor.THREAD_CONTEXT.set(ctx); 284 final List<AbstractConfiguration> configurations = new ArrayList<>(configLocations.size()); 285 for (final URI configLocation : configLocations) { 286 final Configuration currentReadConfiguration = ConfigurationFactory.getInstance() 287 .getConfiguration(ctx, name, configLocation); 288 if (currentReadConfiguration != null) { 289 if (currentReadConfiguration instanceof DefaultConfiguration) { 290 LOGGER.warn("Unable to locate configuration {}, ignoring", configLocation.toString()); 291 } 292 else if (currentReadConfiguration instanceof AbstractConfiguration) { 293 configurations.add((AbstractConfiguration) currentReadConfiguration); 294 } else { 295 LOGGER.error( 296 "Found configuration {}, which is not an AbstractConfiguration and can't be handled by CompositeConfiguration", 297 configLocation); 298 } 299 } else { 300 LOGGER.info("Unable to access configuration {}, ignoring", configLocation.toString()); 301 } 302 } 303 if (configurations.isEmpty()) { 304 LOGGER.error("No configurations could be created for {}", configLocations.toString()); 305 } else if (configurations.size() == 1) { 306 AbstractConfiguration config = configurations.get(0); 307 LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), 308 config.getConfigurationSource().getLocation()); 309 ctx.start(config); 310 } else { 311 final CompositeConfiguration compositeConfiguration = new CompositeConfiguration(configurations); 312 LOGGER.debug("Starting LoggerContext[name={}] from configurations at {}", ctx.getName(), 313 configLocations); 314 ctx.start(compositeConfiguration); 315 } 316 317 ContextAnchor.THREAD_CONTEXT.remove(); 318 } else { 319 ctx.start(); 320 } 321 } 322 return ctx; 323 } 324 325 @Override 326 public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) { 327 if (selector.hasContext(fqcn, loader, currentContext)) { 328 selector.shutdown(fqcn, loader, currentContext, allContexts); 329 } 330 } 331 332 /** 333 * Checks to see if a LoggerContext is installed. 334 * @param fqcn The fully qualified class name of the caller. 335 * @param loader The ClassLoader to use or null. 336 * @param currentContext If true returns the current Context, if false returns the Context appropriate 337 * for the caller if a more appropriate Context can be determined. 338 * @return true if a LoggerContext has been installed, false otherwise. 339 * @since 3.0 340 */ 341 @Override 342 public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) { 343 return selector.hasContext(fqcn, loader, currentContext); 344 } 345 346 /** 347 * Returns the ContextSelector. 348 * @return The ContextSelector. 349 */ 350 public ContextSelector getSelector() { 351 return selector; 352 } 353 354 /** 355 * Returns the ShutdownCallbackRegistry 356 * 357 * @return the ShutdownCallbackRegistry 358 * @since 2.4 359 */ 360 public ShutdownCallbackRegistry getShutdownCallbackRegistry() { 361 return shutdownCallbackRegistry; 362 } 363 364 /** 365 * Removes knowledge of a LoggerContext. 366 * 367 * @param context The context to remove. 368 */ 369 @Override 370 public void removeContext(final org.apache.logging.log4j.spi.LoggerContext context) { 371 if (context instanceof LoggerContext) { 372 selector.removeContext((LoggerContext) context); 373 } 374 } 375 376 @Override 377 public boolean isClassLoaderDependent() { 378 return selector.isClassLoaderDependent(); 379 } 380 381 @Override 382 public Cancellable addShutdownCallback(final Runnable callback) { 383 return isShutdownHookEnabled() ? shutdownCallbackRegistry.addShutdownCallback(callback) : null; 384 } 385 386 public boolean isShutdownHookEnabled() { 387 return SHUTDOWN_HOOK_ENABLED; 388 } 389}