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.Loader; 033import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry; 034import org.apache.logging.log4j.spi.LoggerContextFactory; 035import org.apache.logging.log4j.status.StatusLogger; 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 final String sel = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR); 093 if (sel != null) { 094 try { 095 return Loader.newCheckedInstanceOf(sel, ContextSelector.class); 096 } catch (final Exception ex) { 097 LOGGER.error("Unable to create context {}", sel, ex); 098 } 099 } 100 return new ClassLoaderContextSelector(); 101 } 102 103 private static ShutdownCallbackRegistry createShutdownCallbackRegistry() { 104 // TODO: this is such a common idiom it really deserves a utility method somewhere 105 final String registry = PropertiesUtil.getProperties().getStringProperty( 106 ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY); 107 if (registry != null) { 108 try { 109 return Loader.newCheckedInstanceOf(registry, ShutdownCallbackRegistry.class); 110 } catch (final Exception e) { 111 LOGGER.error(SHUTDOWN_HOOK_MARKER, 112 "There was an error loading the ShutdownCallbackRegistry [{}]. " 113 + "Falling back to DefaultShutdownCallbackRegistry.", registry, e); 114 } 115 } 116 return new DefaultShutdownCallbackRegistry(); 117 } 118 119 private void initializeShutdownCallbackRegistry() { 120 if (SHUTDOWN_HOOK_ENABLED && this.shutdownCallbackRegistry instanceof LifeCycle) { 121 try { 122 ((LifeCycle) this.shutdownCallbackRegistry).start(); 123 } catch (final Exception e) { 124 LOGGER.error("There was an error starting the ShutdownCallbackRegistry.", e); 125 } 126 } 127 } 128 129 /** 130 * Loads the LoggerContext using the ContextSelector. 131 * @param fqcn The fully qualified class name of the caller. 132 * @param loader The ClassLoader to use or null. 133 * @param currentContext If true returns the current Context, if false returns the Context appropriate 134 * for the caller if a more appropriate Context can be determined. 135 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 136 * @return The LoggerContext. 137 */ 138 @Override 139 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 140 final boolean currentContext) { 141 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext); 142 if (externalContext != null && ctx.getExternalContext() == null) { 143 ctx.setExternalContext(externalContext); 144 } 145 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 146 ctx.start(); 147 } 148 return ctx; 149 } 150 151 /** 152 * Loads the LoggerContext using the ContextSelector. 153 * @param fqcn The fully qualified class name of the caller. 154 * @param loader The ClassLoader to use or null. 155 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 156 * @param currentContext If true returns the current Context, if false returns the Context appropriate 157 * for the caller if a more appropriate Context can be determined. 158 * @param source The configuration source. 159 * @return The LoggerContext. 160 */ 161 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 162 final boolean currentContext, final ConfigurationSource source) { 163 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null); 164 if (externalContext != null && ctx.getExternalContext() == null) { 165 ctx.setExternalContext(externalContext); 166 } 167 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 168 if (source != null) { 169 ContextAnchor.THREAD_CONTEXT.set(ctx); 170 final Configuration config = ConfigurationFactory.getInstance().getConfiguration(source); 171 LOGGER.debug("Starting LoggerContext[name={}] from configuration {}", ctx.getName(), source); 172 ctx.start(config); 173 ContextAnchor.THREAD_CONTEXT.remove(); 174 } else { 175 ctx.start(); 176 } 177 } 178 return ctx; 179 } 180 181 /** 182 * Loads the LoggerContext using the ContextSelector using the provided Configuration 183 * @param fqcn The fully qualified class name of the caller. 184 * @param loader The ClassLoader to use or null. 185 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 186 * @param currentContext If true returns the current Context, if false returns the Context appropriate 187 * for the caller if a more appropriate Context can be determined. 188 * @param configuration The Configuration. 189 * @return The LoggerContext. 190 */ 191 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 192 final boolean currentContext, final Configuration configuration) { 193 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null); 194 if (externalContext != null && ctx.getExternalContext() == null) { 195 ctx.setExternalContext(externalContext); 196 } 197 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 198 ContextAnchor.THREAD_CONTEXT.set(ctx); 199 try { 200 ctx.start(configuration); 201 } finally { 202 ContextAnchor.THREAD_CONTEXT.remove(); 203 } 204 } 205 return ctx; 206 } 207 208 /** 209 * Loads the LoggerContext using the ContextSelector. 210 * @param fqcn The fully qualified class name of the caller. 211 * @param loader The ClassLoader to use or null. 212 * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext. 213 * @param currentContext If true returns the current Context, if false returns the Context appropriate 214 * for the caller if a more appropriate Context can be determined. 215 * @param configLocation The location of the configuration for the LoggerContext (or null). 216 * @return The LoggerContext. 217 */ 218 @Override 219 public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, 220 final boolean currentContext, final URI configLocation, final String name) { 221 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, configLocation); 222 if (externalContext != null && ctx.getExternalContext() == null) { 223 ctx.setExternalContext(externalContext); 224 } 225 if (ctx.getState() == LifeCycle.State.INITIALIZED) { 226 if (configLocation != null || name != null) { 227 ContextAnchor.THREAD_CONTEXT.set(ctx); 228 final Configuration config = ConfigurationFactory.getInstance().getConfiguration(name, configLocation); 229 LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation); 230 ctx.start(config); 231 ContextAnchor.THREAD_CONTEXT.remove(); 232 } else { 233 ctx.start(); 234 } 235 } 236 return ctx; 237 } 238 239 /** 240 * Returns the ContextSelector. 241 * @return The ContextSelector. 242 */ 243 public ContextSelector getSelector() { 244 return selector; 245 } 246 247 /** 248 * Returns the ShutdownCallbackRegistry 249 * 250 * @return the ShutdownCallbackRegistry 251 * @since 2.4 252 */ 253 public ShutdownCallbackRegistry getShutdownCallbackRegistry() { 254 return shutdownCallbackRegistry; 255 } 256 257 /** 258 * Removes knowledge of a LoggerContext. 259 * 260 * @param context The context to remove. 261 */ 262 @Override 263 public void removeContext(final org.apache.logging.log4j.spi.LoggerContext context) { 264 if (context instanceof LoggerContext) { 265 selector.removeContext((LoggerContext) context); 266 } 267 } 268 269 @Override 270 public Cancellable addShutdownCallback(final Runnable callback) { 271 return SHUTDOWN_HOOK_ENABLED ? shutdownCallbackRegistry.addShutdownCallback(callback) : null; 272 } 273 274}