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