View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.net.URI;
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.Objects;
23  
24  import org.apache.logging.log4j.core.LifeCycle;
25  import org.apache.logging.log4j.core.LoggerContext;
26  import org.apache.logging.log4j.core.config.AbstractConfiguration;
27  import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
28  import org.apache.logging.log4j.core.config.Configuration;
29  import org.apache.logging.log4j.core.config.ConfigurationFactory;
30  import org.apache.logging.log4j.core.config.ConfigurationSource;
31  import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
32  import org.apache.logging.log4j.core.selector.ContextSelector;
33  import org.apache.logging.log4j.core.util.Cancellable;
34  import org.apache.logging.log4j.core.util.Constants;
35  import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
36  import org.apache.logging.log4j.core.util.Loader;
37  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
38  import org.apache.logging.log4j.spi.LoggerContextFactory;
39  import org.apache.logging.log4j.status.StatusLogger;
40  import org.apache.logging.log4j.util.PropertiesUtil;
41  
42  /**
43   * Factory to locate a ContextSelector and then load a LoggerContext.
44   */
45  public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallbackRegistry {
46  
47      private static final StatusLogger LOGGER = StatusLogger.getLogger();
48      private static final boolean SHUTDOWN_HOOK_ENABLED =
49          PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true) &&
50                  !Constants.IS_WEB_APP;
51  
52      private final ContextSelector selector;
53      private final ShutdownCallbackRegistry shutdownCallbackRegistry;
54  
55      /**
56       * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}.
57       */
58      public Log4jContextFactory() {
59          this(createContextSelector(), createShutdownCallbackRegistry());
60      }
61  
62      /**
63       * Initializes this factory's ContextSelector with the specified selector.
64       * @param selector the selector to use
65       */
66      public Log4jContextFactory(final ContextSelector selector) {
67          this(selector, createShutdownCallbackRegistry());
68      }
69  
70      /**
71       * Constructs a Log4jContextFactory using the ContextSelector from {@link Constants#LOG4J_CONTEXT_SELECTOR}
72       * and the provided ShutdownRegistrationStrategy.
73       *
74       * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
75       * @since 2.1
76       */
77      public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) {
78          this(createContextSelector(), shutdownCallbackRegistry);
79      }
80  
81      /**
82       * Constructs a Log4jContextFactory using the provided ContextSelector and ShutdownRegistrationStrategy.
83       *
84       * @param selector                     the selector to use
85       * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
86       * @since 2.1
87       */
88      public Log4jContextFactory(final ContextSelector selector,
89                                 final ShutdownCallbackRegistry shutdownCallbackRegistry) {
90          this.selector = Objects.requireNonNull(selector, "No ContextSelector provided");
91          this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided");
92          LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass());
93          initializeShutdownCallbackRegistry();
94      }
95  
96      private static ContextSelector createContextSelector() {
97          try {
98              final ContextSelector selector = Loader.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
99                  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 }