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