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