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.ShutdownCallbackRegistry;
37  import org.apache.logging.log4j.spi.LoggerContextFactory;
38  import org.apache.logging.log4j.status.StatusLogger;
39  import org.apache.logging.log4j.util.LoaderUtil;
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  
51      private final ContextSelector selector;
52      private final ShutdownCallbackRegistry shutdownCallbackRegistry;
53  
54      /**
55       * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}.
56       */
57      public Log4jContextFactory() {
58          this(createContextSelector(), createShutdownCallbackRegistry());
59      }
60  
61      /**
62       * Initializes this factory's ContextSelector with the specified selector.
63       * @param selector the selector to use
64       */
65      public Log4jContextFactory(final ContextSelector selector) {
66          this(selector, createShutdownCallbackRegistry());
67      }
68  
69      /**
70       * Constructs a Log4jContextFactory using the ContextSelector from {@link Constants#LOG4J_CONTEXT_SELECTOR}
71       * and the provided ShutdownRegistrationStrategy.
72       *
73       * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
74       * @since 2.1
75       */
76      public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) {
77          this(createContextSelector(), shutdownCallbackRegistry);
78      }
79  
80      /**
81       * Constructs a Log4jContextFactory using the provided ContextSelector and ShutdownRegistrationStrategy.
82       *
83       * @param selector                     the selector to use
84       * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
85       * @since 2.1
86       */
87      public Log4jContextFactory(final ContextSelector selector,
88                                 final ShutdownCallbackRegistry shutdownCallbackRegistry) {
89          this.selector = Objects.requireNonNull(selector, "No ContextSelector provided");
90          this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided");
91          LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass());
92          initializeShutdownCallbackRegistry();
93      }
94  
95      private static ContextSelector createContextSelector() {
96          try {
97              final ContextSelector selector = LoaderUtil.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
98                  ContextSelector.class);
99              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(ctx, 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(ctx, 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                 final List<AbstractConfiguration> configurations = new ArrayList<>(configLocations.size());
262                 for (final URI configLocation : configLocations) {
263                     final Configuration currentReadConfiguration = ConfigurationFactory.getInstance()
264                             .getConfiguration(ctx, 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                 final 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 }