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.config;
018
019import org.apache.logging.log4j.Level;
020import org.apache.logging.log4j.LogManager;
021import org.apache.logging.log4j.Logger;
022import org.apache.logging.log4j.core.LoggerContext;
023import org.apache.logging.log4j.core.impl.Log4jContextFactory;
024import org.apache.logging.log4j.core.util.NetUtils;
025import org.apache.logging.log4j.spi.LoggerContextFactory;
026import org.apache.logging.log4j.status.StatusLogger;
027import org.apache.logging.log4j.util.Strings;
028
029import java.net.URI;
030import java.util.ArrayList;
031import java.util.List;
032import java.util.Map;
033import java.util.concurrent.TimeUnit;
034
035/**
036 * Initializes and configure the Logging system. This class provides several ways to construct a LoggerContext using
037 * the location of a configuration file, a context name, and various optional parameters.
038 */
039public final class Configurator {
040
041    private static final String FQCN = Configurator.class.getName();
042
043    private static final Logger LOGGER = StatusLogger.getLogger();
044
045    private static Log4jContextFactory getFactory() {
046        final LoggerContextFactory factory = LogManager.getFactory();
047        if (factory instanceof Log4jContextFactory) {
048            return (Log4jContextFactory) factory;
049        } else {
050            if (factory != null) {
051                LOGGER.error("LogManager returned an instance of {} which does not implement {}. Unable to initialize Log4j.",
052                        factory.getClass().getName(), Log4jContextFactory.class.getName());
053            } else {
054                LOGGER.fatal("LogManager did not return a LoggerContextFactory. This indicates something has gone terribly wrong!");
055            }
056            return null;
057        }
058    }
059
060    /**
061     * Initializes the Logging Context.
062     * @param loader The ClassLoader for the Context (or null).
063     * @param source The InputSource for the configuration.
064     * @return The LoggerContext.
065     */
066    public static LoggerContext initialize(final ClassLoader loader,
067                                           final ConfigurationSource source) {
068        return initialize(loader, source, null);
069    }
070
071    /**
072     * Initializes the Logging Context.
073     * @param loader The ClassLoader for the Context (or null).
074     * @param source The InputSource for the configuration.
075     * @param externalContext The external context to be attached to the LoggerContext.
076     * @return The LoggerContext.
077     */
078
079    public static LoggerContext initialize(final ClassLoader loader,
080                                           final ConfigurationSource source,
081                                           final Object externalContext)
082    {
083
084        try {
085            final Log4jContextFactory factory = getFactory();
086            return factory == null ? null :
087                    factory.getContext(FQCN, loader, externalContext, false, source);
088        } catch (final Exception ex) {
089            LOGGER.error("There was a problem obtaining a LoggerContext using the configuration source [{}]", source, ex);
090        }
091        return null;
092    }
093
094    /**
095     * Initializes the Logging Context.
096     * @param name The Context name.
097     * @param loader The ClassLoader for the Context (or null).
098     * @param configLocation The configuration for the logging context.
099     * @return The LoggerContext or null if an error occurred (check the status logger).
100     */
101    public static LoggerContext initialize(final String name, final ClassLoader loader, final String configLocation) {
102        return initialize(name, loader, configLocation, null);
103
104    }
105
106    /**
107     * Initializes the Logging Context.
108     * @param name The Context name.
109     * @param loader The ClassLoader for the Context (or null).
110     * @param configLocation The configuration for the logging context (or null, or blank).
111     * @param externalContext The external context to be attached to the LoggerContext
112     * @return The LoggerContext or null if an error occurred (check the status logger).
113     */
114    public static LoggerContext initialize(final String name, final ClassLoader loader, final String configLocation,
115            final Object externalContext) {
116        if (Strings.isBlank(configLocation)) {
117            return initialize(name, loader, (URI) null, externalContext);
118        }
119        if (configLocation.contains(",")) {
120            final String[] parts = configLocation.split(",");
121            String scheme = null;
122            final List<URI> uris = new ArrayList<>(parts.length);
123            for (final String part : parts) {
124                final URI uri = NetUtils.toURI(scheme != null ? scheme + ":" + part.trim() : part.trim());
125                if (scheme == null && uri.getScheme() != null) {
126                    scheme = uri.getScheme();
127                }
128                uris.add(uri);
129            }
130            return initialize(name, loader, uris, externalContext);
131        }
132        return initialize(name, loader, NetUtils.toURI(configLocation), externalContext);
133    }
134
135    /**
136     * Initializes the Logging Context.
137     * @param name The Context name.
138     * @param loader The ClassLoader for the Context (or null).
139     * @param configLocation The configuration for the logging context.
140     * @return The LoggerContext.
141     */
142    public static LoggerContext initialize(final String name, final ClassLoader loader, final URI configLocation) {
143        return initialize(name, loader, configLocation, null);
144    }
145
146    /**
147     * Initializes the Logging Context.
148     * @param name The Context name.
149     * @param loader The ClassLoader for the Context (or null).
150     * @param configLocation The configuration for the logging context (or null).
151     * @param externalContext The external context to be attached to the LoggerContext
152     * @return The LoggerContext.
153     */
154    public static LoggerContext initialize(final String name, final ClassLoader loader, final URI configLocation,
155                                           final Object externalContext) {
156
157        try {
158            final Log4jContextFactory factory = getFactory();
159            return factory == null ? null :
160                    factory.getContext(FQCN, loader, externalContext, false, configLocation, name);
161        } catch (final Exception ex) {
162            LOGGER.error("There was a problem initializing the LoggerContext [{}] using configuration at [{}].",
163                    name, configLocation, ex);
164        }
165        return null;
166    }
167
168    /**
169     * Initializes the Logging Context.
170     * @param name The Context name.
171     * @param loader The ClassLoader for the Context (or null).
172     * @param configLocation The configuration for the logging context (or null).
173     * @param entry The external context entry to be attached to the LoggerContext
174     * @return The LoggerContext.
175     */
176    public static LoggerContext initialize(final String name, final ClassLoader loader, final URI configLocation,
177            final Map.Entry<String, Object> entry) {
178
179        try {
180            final Log4jContextFactory factory = getFactory();
181            return factory == null ? null :
182                    factory.getContext(FQCN, loader, entry, false, configLocation, name);
183        } catch (final Exception ex) {
184            LOGGER.error("There was a problem initializing the LoggerContext [{}] using configuration at [{}].",
185                    name, configLocation, ex);
186        }
187        return null;
188    }
189
190    public static LoggerContext initialize(final String name, final ClassLoader loader, final List<URI> configLocations,
191            final Object externalContext) {
192        try {
193            final Log4jContextFactory factory = getFactory();
194            return factory == null ?
195                    null :
196                    factory.getContext(FQCN, loader, externalContext, false, configLocations, name);
197        } catch (final Exception ex) {
198            LOGGER.error("There was a problem initializing the LoggerContext [{}] using configurations at [{}].", name,
199                    configLocations, ex);
200        }
201        return null;
202    }
203
204    /**
205     * Initializes the Logging Context.
206     * @param name The Context name.
207     * @param configLocation The configuration for the logging context.
208     * @return The LoggerContext or null if an error occurred (check the status logger).
209     */
210    public static LoggerContext initialize(final String name, final String configLocation) {
211        return initialize(name, null, configLocation);
212    }
213
214    /**
215     * Initializes the Logging Context.
216     * @param configuration The Configuration.
217     * @return The LoggerContext.
218     */
219    public static LoggerContext initialize(final Configuration configuration) {
220        return initialize(null, configuration, null);
221    }
222
223    /**
224     * Initializes the Logging Context.
225     * @param loader The ClassLoader.
226     * @param configuration The Configuration.
227     * @return The LoggerContext.
228     */
229    public static LoggerContext initialize(final ClassLoader loader, final Configuration configuration) {
230        return initialize(loader, configuration, null);
231    }
232
233    /**
234     * Initializes the Logging Context.
235     * @param loader The ClassLoader.
236     * @param configuration The Configuration.
237     * @param externalContext - The external context to be attached to the LoggerContext.
238     * @return The LoggerContext.
239     */
240    public static LoggerContext initialize(final ClassLoader loader, final Configuration configuration, final Object externalContext) {
241        try {
242            final Log4jContextFactory factory = getFactory();
243            return factory == null ? null :
244                    factory.getContext(FQCN, loader, externalContext, false, configuration);
245        } catch (final Exception ex) {
246            LOGGER.error("There was a problem initializing the LoggerContext using configuration {}",
247                    configuration.getName(), ex);
248        }
249        return null;
250    }
251
252    /**
253     * Reconfigure using an already constructed Configuration.
254     * @param configuration The configuration.
255     * @since 2.13.0
256     */
257    public static void reconfigure(final Configuration configuration) {
258        try {
259            final Log4jContextFactory factory = getFactory();
260            if (factory != null) {
261                factory.getContext(FQCN, null, null, false)
262                        .reconfigure(configuration);
263            }
264        } catch (final Exception ex) {
265            LOGGER.error("There was a problem initializing the LoggerContext using configuration {}",
266                    configuration.getName(), ex);
267        }
268    }
269
270    /**
271     * Reload the existing reconfiguration.
272     * @since 2.12.0
273     */
274    public static void reconfigure() {
275        try {
276            Log4jContextFactory factory = getFactory();
277            if (factory != null) {
278                factory.getSelector().getContext(FQCN, null, false).reconfigure();
279            } else {
280                LOGGER.warn("Unable to reconfigure - Log4j has not been initialized.");
281            }
282        } catch (final Exception ex) {
283            LOGGER.error("Error encountered trying to reconfigure logging", ex);
284        }
285    }
286
287    /**
288     * Reconfigure with a potentially new configuration.
289     * @param uri The location of the configuration.
290     * @since 2.12.0
291     */
292    public static void reconfigure(final URI uri) {
293        try {
294            Log4jContextFactory factory = getFactory();
295            if (factory != null) {
296                factory.getSelector().getContext(FQCN, null, false).setConfigLocation(uri);
297            } else {
298                LOGGER.warn("Unable to reconfigure - Log4j has not been initialized.");
299            }
300        } catch (final Exception ex) {
301            LOGGER.error("Error encountered trying to reconfigure logging", ex);
302        }
303    }
304
305    /**
306     * Sets the levels of <code>parentLogger</code> and all 'child' loggers to the given <code>level</code>.
307     * @param parentLogger the parent logger
308     * @param level the new level
309     */
310    public static void setAllLevels(final String parentLogger, final Level level) {
311        // 1) get logger config
312        // 2) if exact match, use it, if not, create it.
313        // 3) set level on logger config
314        // 4) update child logger configs with level
315        // 5) update loggers
316        final LoggerContext loggerContext = LoggerContext.getContext(false);
317        final Configuration config = loggerContext.getConfiguration();
318        boolean set = setLevel(parentLogger, level, config);
319        for (final Map.Entry<String, LoggerConfig> entry : config.getLoggers().entrySet()) {
320            if (entry.getKey().startsWith(parentLogger)) {
321                set |= setLevel(entry.getValue(), level);
322            }
323        }
324        if (set) {
325            loggerContext.updateLoggers();
326        }
327    }
328
329    private static boolean setLevel(final LoggerConfig loggerConfig, final Level level) {
330        final boolean set = !loggerConfig.getLevel().equals(level);
331        if (set) {
332            loggerConfig.setLevel(level);
333        }
334        return set;
335    }
336
337    /**
338     * Sets logger levels.
339     *
340     * @param levelMap
341     *            a levelMap where keys are level names and values are new
342     *            Levels.
343     */
344    public static void setLevel(final Map<String, Level> levelMap) {
345        final LoggerContext loggerContext = LoggerContext.getContext(false);
346        final Configuration config = loggerContext.getConfiguration();
347        boolean set = false;
348        for (final Map.Entry<String, Level> entry : levelMap.entrySet()) {
349            final String loggerName = entry.getKey();
350            final Level level = entry.getValue();
351            set |= setLevel(loggerName, level, config);
352        }
353        if (set) {
354            loggerContext.updateLoggers();
355        }
356    }
357
358    /**
359     * Sets a logger's level.
360     *
361     * @param loggerName
362     *            the logger name
363     * @param level
364     *            the new level
365     */
366    public static void setLevel(final String loggerName, final Level level) {
367        final LoggerContext loggerContext = LoggerContext.getContext(false);
368        if (Strings.isEmpty(loggerName)) {
369            setRootLevel(level);
370        } else if (setLevel(loggerName, level, loggerContext.getConfiguration())) {
371            loggerContext.updateLoggers();
372        }
373    }
374
375    private static boolean setLevel(final String loggerName, final Level level, final Configuration config) {
376        boolean set;
377        LoggerConfig loggerConfig = config.getLoggerConfig(loggerName);
378        if (!loggerName.equals(loggerConfig.getName())) {
379            // TODO Should additivity be inherited?
380            loggerConfig = new LoggerConfig(loggerName, level, true);
381            config.addLogger(loggerName, loggerConfig);
382            loggerConfig.setLevel(level);
383            set = true;
384        } else {
385            set = setLevel(loggerConfig, level);
386        }
387        return set;
388    }
389
390    /**
391     * Sets the root logger's level.
392     *
393     * @param level
394     *            the new level
395     */
396    public static void setRootLevel(final Level level) {
397        final LoggerContext loggerContext = LoggerContext.getContext(false);
398        final LoggerConfig loggerConfig = loggerContext.getConfiguration().getRootLogger();
399        if (!loggerConfig.getLevel().equals(level)) {
400            loggerConfig.setLevel(level);
401            loggerContext.updateLoggers();
402        }
403    }
404
405    /**
406     * Shuts down the given logger context. This request does not wait for Log4j tasks to complete.
407     * <p>
408     * Log4j starts threads to perform certain actions like file rollovers; calling this method will not wait until the
409     * rollover thread is done. When this method returns, these tasks' status are undefined, the tasks may be done or
410     * not.
411     * </p>
412     *
413     * @param ctx
414     *            the logger context to shut down, may be null.
415     */
416    public static void shutdown(final LoggerContext ctx) {
417        if (ctx != null) {
418            ctx.stop();
419        }
420    }
421
422    /**
423     * Shuts down the given logger context.
424     * <p>
425     * Log4j can start threads to perform certain actions like file rollovers; calling this method with a positive
426     * timeout will block until the rollover thread is done.
427     * </p>
428     *
429     * @param ctx
430     *            the logger context to shut down, may be null.
431     * @param timeout
432     *            the maximum time to wait
433     * @param timeUnit
434     *            the time unit of the timeout argument
435     * @return {@code true} if the logger context terminated and {@code false} if the timeout elapsed before
436     *         termination.
437     *
438     * @see LoggerContext#stop(long, TimeUnit)
439     *
440     * @since 2.7
441     */
442    public static boolean shutdown(final LoggerContext ctx, final long timeout, final TimeUnit timeUnit) {
443        if (ctx != null) {
444            return ctx.stop(timeout, timeUnit);
445        }
446        return true;
447    }
448
449    private Configurator() {
450        // empty
451    }
452}