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.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024
025import org.apache.logging.log4j.core.LifeCycle;
026import org.apache.logging.log4j.core.LoggerContext;
027import org.apache.logging.log4j.core.config.AbstractConfiguration;
028import org.apache.logging.log4j.core.config.DefaultConfiguration;
029import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
030import org.apache.logging.log4j.core.config.Configuration;
031import org.apache.logging.log4j.core.config.ConfigurationFactory;
032import org.apache.logging.log4j.core.config.ConfigurationSource;
033import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
034import org.apache.logging.log4j.core.selector.ContextSelector;
035import org.apache.logging.log4j.core.util.Cancellable;
036import org.apache.logging.log4j.core.util.Constants;
037import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
038import org.apache.logging.log4j.core.util.Loader;
039import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
040import org.apache.logging.log4j.spi.LoggerContextFactory;
041import org.apache.logging.log4j.status.StatusLogger;
042import org.apache.logging.log4j.util.PropertiesUtil;
043
044/**
045 * Factory to locate a ContextSelector and then load a LoggerContext.
046 */
047public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallbackRegistry {
048
049    private static final StatusLogger LOGGER = StatusLogger.getLogger();
050    private static final boolean SHUTDOWN_HOOK_ENABLED =
051        PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true) &&
052                !Constants.IS_WEB_APP;
053
054    private final ContextSelector selector;
055    private final ShutdownCallbackRegistry shutdownCallbackRegistry;
056
057    /**
058     * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}.
059     */
060    public Log4jContextFactory() {
061        this(createContextSelector(), createShutdownCallbackRegistry());
062    }
063
064    /**
065     * Initializes this factory's ContextSelector with the specified selector.
066     * @param selector the selector to use
067     */
068    public Log4jContextFactory(final ContextSelector selector) {
069        this(selector, createShutdownCallbackRegistry());
070    }
071
072    /**
073     * Constructs a Log4jContextFactory using the ContextSelector from {@link Constants#LOG4J_CONTEXT_SELECTOR}
074     * and the provided ShutdownRegistrationStrategy.
075     *
076     * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
077     * @since 2.1
078     */
079    public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) {
080        this(createContextSelector(), shutdownCallbackRegistry);
081    }
082
083    /**
084     * Constructs a Log4jContextFactory using the provided ContextSelector and ShutdownRegistrationStrategy.
085     *
086     * @param selector                     the selector to use
087     * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
088     * @since 2.1
089     */
090    public Log4jContextFactory(final ContextSelector selector,
091                               final ShutdownCallbackRegistry shutdownCallbackRegistry) {
092        this.selector = Objects.requireNonNull(selector, "No ContextSelector provided");
093        this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided");
094        LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass());
095        initializeShutdownCallbackRegistry();
096    }
097
098    private static ContextSelector createContextSelector() {
099        try {
100            final ContextSelector selector = Loader.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
101                ContextSelector.class);
102            if (selector != null) {
103                return selector;
104            }
105        } catch (final Exception e) {
106            LOGGER.error("Unable to create custom ContextSelector. Falling back to default.", e);
107        }
108        return new ClassLoaderContextSelector();
109    }
110
111    private static ShutdownCallbackRegistry createShutdownCallbackRegistry() {
112        try {
113            final ShutdownCallbackRegistry registry = Loader.newCheckedInstanceOfProperty(
114                ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY, ShutdownCallbackRegistry.class
115            );
116            if (registry != null) {
117                return registry;
118            }
119        } catch (final Exception e) {
120            LOGGER.error("Unable to create custom ShutdownCallbackRegistry. Falling back to default.", e);
121        }
122        return new DefaultShutdownCallbackRegistry();
123    }
124
125    private void initializeShutdownCallbackRegistry() {
126        if (isShutdownHookEnabled() && this.shutdownCallbackRegistry instanceof LifeCycle) {
127            try {
128                ((LifeCycle) this.shutdownCallbackRegistry).start();
129            } catch (final IllegalStateException e) {
130                LOGGER.error("Cannot start ShutdownCallbackRegistry, already shutting down.");
131                throw e;
132            } catch (final RuntimeException e) {
133                LOGGER.error("There was an error starting the ShutdownCallbackRegistry.", e);
134            }
135        }
136    }
137
138    /**
139     * Loads the LoggerContext using the ContextSelector.
140     * @param fqcn The fully qualified class name of the caller.
141     * @param loader The ClassLoader to use or null.
142     * @param currentContext If true returns the current Context, if false returns the Context appropriate
143     * for the caller if a more appropriate Context can be determined.
144     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
145     * @return The LoggerContext.
146     */
147    @Override
148    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
149                                    final boolean currentContext) {
150        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
151        if (externalContext != null && ctx.getExternalContext() == null) {
152            ctx.setExternalContext(externalContext);
153        }
154        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
155            ctx.start();
156        }
157        return ctx;
158    }
159
160    /**
161     * Loads the LoggerContext using the ContextSelector.
162     * @param fqcn The fully qualified class name of the caller.
163     * @param loader The ClassLoader to use or null.
164     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
165     * @param currentContext If true returns the current Context, if false returns the Context appropriate
166     * for the caller if a more appropriate Context can be determined.
167     * @param source The configuration source.
168     * @return The LoggerContext.
169     */
170    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
171                                    final boolean currentContext, final ConfigurationSource source) {
172        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null);
173        if (externalContext != null && ctx.getExternalContext() == null) {
174            ctx.setExternalContext(externalContext);
175        }
176        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
177            if (source != null) {
178                ContextAnchor.THREAD_CONTEXT.set(ctx);
179                final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, source);
180                LOGGER.debug("Starting LoggerContext[name={}] from configuration {}", ctx.getName(), source);
181                ctx.start(config);
182                ContextAnchor.THREAD_CONTEXT.remove();
183            } else {
184                ctx.start();
185            }
186        }
187        return ctx;
188    }
189
190    /**
191     * Loads the LoggerContext using the ContextSelector using the provided Configuration
192     * @param fqcn The fully qualified class name of the caller.
193     * @param loader The ClassLoader to use or null.
194     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
195     * @param currentContext If true returns the current Context, if false returns the Context appropriate
196     * for the caller if a more appropriate Context can be determined.
197     * @param configuration The Configuration.
198     * @return The LoggerContext.
199     */
200    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
201            final boolean currentContext, final Configuration configuration) {
202        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, null);
203        if (externalContext != null && ctx.getExternalContext() == null) {
204            ctx.setExternalContext(externalContext);
205        }
206        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
207            ContextAnchor.THREAD_CONTEXT.set(ctx);
208            try {
209                ctx.start(configuration);
210            } finally {
211                ContextAnchor.THREAD_CONTEXT.remove();
212            }
213        }
214        return ctx;
215    }
216
217    /**
218     * Loads the LoggerContext using the ContextSelector.
219     * @param fqcn The fully qualified class name of the caller.
220     * @param loader The ClassLoader to use or null.
221     * @param externalContext An external context (such as a ServletContext) to be associated with the LoggerContext.
222     * @param currentContext If true returns the current Context, if false returns the Context appropriate
223     * for the caller if a more appropriate Context can be determined.
224     * @param configLocation The location of the configuration for the LoggerContext (or null).
225     * @return The LoggerContext.
226     */
227    @Override
228    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
229                                    final boolean currentContext, final URI configLocation, final String name) {
230        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext, configLocation);
231        if (externalContext != null && ctx.getExternalContext() == null) {
232            ctx.setExternalContext(externalContext);
233        }
234        if (name != null) {
235                ctx.setName(name);
236        }
237        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
238            if (configLocation != null || name != null) {
239                ContextAnchor.THREAD_CONTEXT.set(ctx);
240                final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, name, configLocation);
241                LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation);
242                ctx.start(config);
243                ContextAnchor.THREAD_CONTEXT.remove();
244            } else {
245                ctx.start();
246            }
247        }
248        return ctx;
249    }
250
251    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Map.Entry<String, Object> entry,
252            final boolean currentContext, final URI configLocation, final String name) {
253        final LoggerContext ctx = selector.getContext(fqcn, loader, entry, currentContext, configLocation);
254        if (name != null) {
255            ctx.setName(name);
256        }
257        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
258            if (configLocation != null || name != null) {
259                ContextAnchor.THREAD_CONTEXT.set(ctx);
260                final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, name, configLocation);
261                LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(), configLocation);
262                ctx.start(config);
263                ContextAnchor.THREAD_CONTEXT.remove();
264            } else {
265                ctx.start();
266            }
267        }
268        return ctx;
269    }
270
271    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
272            final boolean currentContext, final List<URI> configLocations, final String name) {
273        final LoggerContext ctx = selector
274                .getContext(fqcn, loader, currentContext, null/*this probably needs to change*/);
275        if (externalContext != null && ctx.getExternalContext() == null) {
276            ctx.setExternalContext(externalContext);
277        }
278        if (name != null) {
279            ctx.setName(name);
280        }
281        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
282            if ((configLocations != null && !configLocations.isEmpty())) {
283                ContextAnchor.THREAD_CONTEXT.set(ctx);
284                final List<AbstractConfiguration> configurations = new ArrayList<>(configLocations.size());
285                for (final URI configLocation : configLocations) {
286                    final Configuration currentReadConfiguration = ConfigurationFactory.getInstance()
287                            .getConfiguration(ctx, name, configLocation);
288                    if (currentReadConfiguration != null) {
289                        if (currentReadConfiguration instanceof DefaultConfiguration) {
290                            LOGGER.warn("Unable to locate configuration {}, ignoring", configLocation.toString());
291                        }
292                        else if (currentReadConfiguration instanceof AbstractConfiguration) {
293                            configurations.add((AbstractConfiguration) currentReadConfiguration);
294                        } else {
295                            LOGGER.error(
296                                    "Found configuration {}, which is not an AbstractConfiguration and can't be handled by CompositeConfiguration",
297                                    configLocation);
298                        }
299                    } else {
300                        LOGGER.info("Unable to access configuration {}, ignoring", configLocation.toString());
301                    }
302                }
303                if (configurations.isEmpty()) {
304                    LOGGER.error("No configurations could be created for {}", configLocations.toString());
305                } else if (configurations.size() == 1) {
306                    AbstractConfiguration config = configurations.get(0);
307                    LOGGER.debug("Starting LoggerContext[name={}] from configuration at {}", ctx.getName(),
308                            config.getConfigurationSource().getLocation());
309                    ctx.start(config);
310                } else {
311                    final CompositeConfiguration compositeConfiguration = new CompositeConfiguration(configurations);
312                    LOGGER.debug("Starting LoggerContext[name={}] from configurations at {}", ctx.getName(),
313                            configLocations);
314                    ctx.start(compositeConfiguration);
315                }
316
317                ContextAnchor.THREAD_CONTEXT.remove();
318            } else {
319                ctx.start();
320            }
321        }
322        return ctx;
323    }
324
325    @Override
326    public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
327        if (selector.hasContext(fqcn, loader, currentContext)) {
328            selector.shutdown(fqcn, loader, currentContext, allContexts);
329        }
330    }
331
332    /**
333     * Checks to see if a LoggerContext is installed.
334     * @param fqcn The fully qualified class name of the caller.
335     * @param loader The ClassLoader to use or null.
336     * @param currentContext If true returns the current Context, if false returns the Context appropriate
337     * for the caller if a more appropriate Context can be determined.
338     * @return true if a LoggerContext has been installed, false otherwise.
339     * @since 3.0
340     */
341    @Override
342    public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
343        return selector.hasContext(fqcn, loader, currentContext);
344    }
345
346    /**
347     * Returns the ContextSelector.
348     * @return The ContextSelector.
349     */
350    public ContextSelector getSelector() {
351        return selector;
352    }
353
354        /**
355         * Returns the ShutdownCallbackRegistry
356         *
357         * @return the ShutdownCallbackRegistry
358         * @since 2.4
359         */
360        public ShutdownCallbackRegistry getShutdownCallbackRegistry() {
361                return shutdownCallbackRegistry;
362        }
363
364    /**
365     * Removes knowledge of a LoggerContext.
366     *
367     * @param context The context to remove.
368     */
369    @Override
370    public void removeContext(final org.apache.logging.log4j.spi.LoggerContext context) {
371        if (context instanceof LoggerContext) {
372            selector.removeContext((LoggerContext) context);
373        }
374    }
375
376    @Override
377    public Cancellable addShutdownCallback(final Runnable callback) {
378        return isShutdownHookEnabled() ? shutdownCallbackRegistry.addShutdownCallback(callback) : null;
379    }
380
381    public boolean isShutdownHookEnabled() {
382        return SHUTDOWN_HOOK_ENABLED;
383    }
384}