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