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.Objects;
21  
22  import org.apache.logging.log4j.core.LifeCycle;
23  import org.apache.logging.log4j.core.LoggerContext;
24  import org.apache.logging.log4j.core.config.Configuration;
25  import org.apache.logging.log4j.core.config.ConfigurationFactory;
26  import org.apache.logging.log4j.core.config.ConfigurationSource;
27  import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
28  import org.apache.logging.log4j.core.selector.ContextSelector;
29  import org.apache.logging.log4j.core.util.Cancellable;
30  import org.apache.logging.log4j.core.util.Constants;
31  import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
32  import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
33  import org.apache.logging.log4j.spi.LoggerContextFactory;
34  import org.apache.logging.log4j.status.StatusLogger;
35  import org.apache.logging.log4j.util.LoaderUtil;
36  import org.apache.logging.log4j.util.PropertiesUtil;
37  
38  /**
39   * Factory to locate a ContextSelector and then load a LoggerContext.
40   */
41  public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallbackRegistry {
42  
43      private static final StatusLogger LOGGER = StatusLogger.getLogger();
44      private static final boolean SHUTDOWN_HOOK_ENABLED =
45          PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true);
46  
47      private final ContextSelector selector;
48      private final ShutdownCallbackRegistry shutdownCallbackRegistry;
49  
50      /**
51       * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}.
52       */
53      public Log4jContextFactory() {
54          this(createContextSelector(), createShutdownCallbackRegistry());
55      }
56  
57      /**
58       * Initializes this factory's ContextSelector with the specified selector.
59       * @param selector the selector to use
60       */
61      public Log4jContextFactory(final ContextSelector selector) {
62          this(selector, createShutdownCallbackRegistry());
63      }
64  
65      /**
66       * Constructs a Log4jContextFactory using the ContextSelector from {@link Constants#LOG4J_CONTEXT_SELECTOR}
67       * and the provided ShutdownRegistrationStrategy.
68       *
69       * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
70       * @since 2.1
71       */
72      public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) {
73          this(createContextSelector(), shutdownCallbackRegistry);
74      }
75  
76      /**
77       * Constructs a Log4jContextFactory using the provided ContextSelector and ShutdownRegistrationStrategy.
78       *
79       * @param selector                     the selector to use
80       * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
81       * @since 2.1
82       */
83      public Log4jContextFactory(final ContextSelector selector,
84                                 final ShutdownCallbackRegistry shutdownCallbackRegistry) {
85          this.selector = Objects.requireNonNull(selector, "No ContextSelector provided");
86          this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided");
87          LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass());
88          initializeShutdownCallbackRegistry();
89      }
90  
91      private static ContextSelector createContextSelector() {
92          try {
93              final ContextSelector selector = LoaderUtil.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
94                  ContextSelector.class);
95              if (selector != null) {
96                  return selector;
97              }
98          } catch (final Exception e) {
99              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 }