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.Objects;
023
024import org.apache.logging.log4j.core.LifeCycle;
025import org.apache.logging.log4j.core.LoggerContext;
026import org.apache.logging.log4j.core.config.AbstractConfiguration;
027import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
028import org.apache.logging.log4j.core.config.Configuration;
029import org.apache.logging.log4j.core.config.ConfigurationFactory;
030import org.apache.logging.log4j.core.config.ConfigurationSource;
031import org.apache.logging.log4j.core.selector.ClassLoaderContextSelector;
032import org.apache.logging.log4j.core.selector.ContextSelector;
033import org.apache.logging.log4j.core.util.Cancellable;
034import org.apache.logging.log4j.core.util.Constants;
035import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
036import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
037import org.apache.logging.log4j.spi.LoggerContextFactory;
038import org.apache.logging.log4j.status.StatusLogger;
039import org.apache.logging.log4j.util.LoaderUtil;
040import org.apache.logging.log4j.util.PropertiesUtil;
041
042/**
043 * Factory to locate a ContextSelector and then load a LoggerContext.
044 */
045public class Log4jContextFactory implements LoggerContextFactory, ShutdownCallbackRegistry {
046
047    private static final StatusLogger LOGGER = StatusLogger.getLogger();
048    private static final boolean SHUTDOWN_HOOK_ENABLED =
049        PropertiesUtil.getProperties().getBooleanProperty(ShutdownCallbackRegistry.SHUTDOWN_HOOK_ENABLED, true) &&
050                !Constants.IS_WEB_APP;
051
052    private final ContextSelector selector;
053    private final ShutdownCallbackRegistry shutdownCallbackRegistry;
054
055    /**
056     * Initializes the ContextSelector from system property {@link Constants#LOG4J_CONTEXT_SELECTOR}.
057     */
058    public Log4jContextFactory() {
059        this(createContextSelector(), createShutdownCallbackRegistry());
060    }
061
062    /**
063     * Initializes this factory's ContextSelector with the specified selector.
064     * @param selector the selector to use
065     */
066    public Log4jContextFactory(final ContextSelector selector) {
067        this(selector, createShutdownCallbackRegistry());
068    }
069
070    /**
071     * Constructs a Log4jContextFactory using the ContextSelector from {@link Constants#LOG4J_CONTEXT_SELECTOR}
072     * and the provided ShutdownRegistrationStrategy.
073     *
074     * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
075     * @since 2.1
076     */
077    public Log4jContextFactory(final ShutdownCallbackRegistry shutdownCallbackRegistry) {
078        this(createContextSelector(), shutdownCallbackRegistry);
079    }
080
081    /**
082     * Constructs a Log4jContextFactory using the provided ContextSelector and ShutdownRegistrationStrategy.
083     *
084     * @param selector                     the selector to use
085     * @param shutdownCallbackRegistry the ShutdownRegistrationStrategy to use
086     * @since 2.1
087     */
088    public Log4jContextFactory(final ContextSelector selector,
089                               final ShutdownCallbackRegistry shutdownCallbackRegistry) {
090        this.selector = Objects.requireNonNull(selector, "No ContextSelector provided");
091        this.shutdownCallbackRegistry = Objects.requireNonNull(shutdownCallbackRegistry, "No ShutdownCallbackRegistry provided");
092        LOGGER.debug("Using ShutdownCallbackRegistry {}", shutdownCallbackRegistry.getClass());
093        initializeShutdownCallbackRegistry();
094    }
095
096    private static ContextSelector createContextSelector() {
097        try {
098            final ContextSelector selector = LoaderUtil.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
099                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 = LoaderUtil.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}