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