001/*
002 * (C) Copyright 2011-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Julien Carsique
018 *
019 */
020
021package org.nuxeo.log4j;
022
023import java.io.File;
024import java.net.MalformedURLException;
025import java.util.ArrayList;
026import java.util.Enumeration;
027
028import org.apache.commons.lang.ArrayUtils;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.apache.log4j.Appender;
032import org.apache.log4j.FileAppender;
033import org.apache.log4j.Hierarchy;
034import org.apache.log4j.Level;
035import org.apache.log4j.LogManager;
036import org.apache.log4j.Logger;
037import org.apache.log4j.PatternLayout;
038import org.apache.log4j.spi.DefaultRepositorySelector;
039import org.apache.log4j.spi.Filter;
040import org.apache.log4j.spi.LoggerRepository;
041import org.apache.log4j.spi.RootLogger;
042import org.apache.log4j.varia.LevelRangeFilter;
043import org.apache.log4j.xml.DOMConfigurator;
044
045/**
046 * Provides helper methods for working with log4j
047 *
048 * @author jcarsique
049 * @since 5.4.2
050 */
051public class Log4JHelper {
052    private static final Log log = LogFactory.getLog(Log4JHelper.class);
053
054    public static final String CONSOLE_APPENDER_NAME = "CONSOLE";
055
056    protected static final String FULL_PATTERN_LAYOUT = "%d{HH:mm:ss,SSS} %-5p [%l] %m%n";
057
058    protected static final String LIGHT_PATTERN_LAYOUT = "%m%n";
059
060    /**
061     * Returns list of files produced by {@link FileAppender}s defined in a given {@link LoggerRepository}. There's no
062     * need for the log4j configuration corresponding to this repository of being active.
063     *
064     * @param loggerRepository {@link LoggerRepository} to browse looking for {@link FileAppender}
065     * @return {@link FileAppender}s configured in loggerRepository
066     */
067    public static ArrayList<String> getFileAppendersFiles(LoggerRepository loggerRepository) {
068        ArrayList<String> logFiles = new ArrayList<>();
069        for (Enumeration<Appender> appenders = loggerRepository.getRootLogger().getAllAppenders(); appenders.hasMoreElements();) {
070            Appender appender = appenders.nextElement();
071            if (appender instanceof FileAppender) {
072                FileAppender fileAppender = (FileAppender) appender;
073                logFiles.add(fileAppender.getFile());
074            }
075        }
076        Enumeration<Logger> currentLoggers = loggerRepository.getCurrentLoggers();
077        while (currentLoggers.hasMoreElements()) {
078            Logger logger = (currentLoggers.nextElement());
079            for (Enumeration<Appender> appenders = logger.getAllAppenders(); appenders.hasMoreElements();) {
080                Appender appender = appenders.nextElement();
081                if (appender instanceof FileAppender) {
082                    FileAppender fileAppender = (FileAppender) appender;
083                    logFiles.add(fileAppender.getFile());
084                }
085            }
086        }
087        return logFiles;
088    }
089
090    /**
091     * Creates a {@link LoggerRepository} initialized with given log4j configuration file without making this
092     * configuration active.
093     *
094     * @param log4jConfigurationFile XML Log4J configuration file to load.
095     * @return {@link LoggerRepository} initialized with log4jConfigurationFile
096     */
097    public static LoggerRepository getNewLoggerRepository(File log4jConfigurationFile) {
098        LoggerRepository loggerRepository = null;
099        try {
100            loggerRepository = new DefaultRepositorySelector(new Hierarchy(new RootLogger(Level.DEBUG))).getLoggerRepository();
101            if (log4jConfigurationFile == null || !log4jConfigurationFile.exists()) {
102                log.error("Missing Log4J configuration: " + log4jConfigurationFile);
103            } else {
104                new DOMConfigurator().doConfigure(log4jConfigurationFile.toURI().toURL(), loggerRepository);
105                log.debug("Log4j configuration " + log4jConfigurationFile + " successfully loaded.");
106            }
107        } catch (MalformedURLException e) {
108            log.error("Could not load " + log4jConfigurationFile, e);
109        }
110        return loggerRepository;
111    }
112
113    /**
114     * @see #getFileAppendersFiles(LoggerRepository)
115     * @param log4jConfigurationFile
116     * @return {@link FileAppender}s defined in log4jConfigurationFile
117     */
118    public static ArrayList<String> getFileAppendersFiles(File log4jConfigurationFile) {
119        return getFileAppendersFiles(getNewLoggerRepository(log4jConfigurationFile));
120    }
121
122    /**
123     * Set DEBUG level on the given category and the children categories. Also change the pattern layout of the given
124     * appenderName.
125     *
126     * @since 5.6
127     * @param categories Log4J categories for which to switch debug log level (comma separated values)
128     * @param debug set debug log level to true or false
129     * @param includeChildren Also set/unset debug mode on children categories
130     * @param appenderNames Appender names on which to set a detailed pattern layout. Ignored if null.
131     */
132    public static void setDebug(String categories, boolean debug, boolean includeChildren, String[] appenderNames) {
133        setDebug(categories.split(","), debug, includeChildren, appenderNames);
134    }
135
136    /**
137     * @param categories
138     * @param debug
139     * @param includeChildren
140     * @param appenderNames
141     * @since 7.4
142     */
143    public static void setDebug(String[] categories, boolean debug, boolean includeChildren, String[] appenderNames) {
144        Level newLevel = debug ? Level.DEBUG : Level.INFO;
145
146        // Manage categories
147        for (String category : categories) { // Create non existing loggers
148            Logger logger = Logger.getLogger(category);
149            logger.setLevel(newLevel);
150            log.info("Log level set to " + newLevel + " for: " + logger.getName());
151        }
152        if (includeChildren) { // Also change children categories' level
153            for (Enumeration<Logger> loggers = LogManager.getCurrentLoggers(); loggers.hasMoreElements();) {
154                Logger logger = loggers.nextElement();
155                if (logger.getLevel() == newLevel) {
156                    continue;
157                }
158                for (String category : categories) {
159                    if (logger.getName().startsWith(category)) {
160                        logger.setLevel(newLevel);
161                        log.info("Log level set to " + newLevel + " for: " + logger.getName());
162                        break;
163                    }
164                }
165            }
166        }
167
168        // Manage appenders
169        if (ArrayUtils.isEmpty(appenderNames)) {
170            return;
171        }
172        for (String appenderName : appenderNames) {
173            Appender consoleAppender = Logger.getRootLogger().getAppender(appenderName);
174            if (consoleAppender != null) {
175                Filter filter = consoleAppender.getFilter();
176                while (filter != null && !(filter instanceof LevelRangeFilter)) {
177                    filter = filter.getNext();
178                }
179                if (filter != null) {
180                    LevelRangeFilter levelRangeFilter = (LevelRangeFilter) filter;
181                    levelRangeFilter.setLevelMin(newLevel);
182                    log.debug(String.format("Log level filter set to %s for appender %s", newLevel, appenderName));
183                }
184                String patternLayout = debug ? FULL_PATTERN_LAYOUT : LIGHT_PATTERN_LAYOUT;
185                consoleAppender.setLayout(new PatternLayout(patternLayout));
186                log.info(String.format("Set pattern layout of %s to %s", appenderName, patternLayout));
187            }
188        }
189    }
190
191    /**
192     * Set DEBUG level on the given category and change pattern layout of {@link #CONSOLE_APPENDER_NAME} if defined.
193     * Children categories are unchanged.
194     *
195     * @since 5.5
196     * @param category Log4J category for which to switch debug log level
197     * @param debug set debug log level to true or false
198     * @see #setDebug(String, boolean, boolean, String[])
199     */
200    public static void setDebug(String category, boolean debug) {
201        setDebug(category, debug, false, new String[] { CONSOLE_APPENDER_NAME });
202    }
203
204    /**
205     * Set "quiet" mode: set log level to WARN for the given Log4J appender.
206     *
207     * @param appenderName Log4J appender to switch to WARN
208     * @since 5.5
209     */
210    public static void setQuiet(String appenderName) {
211        Appender appender = Logger.getRootLogger().getAppender(appenderName);
212        if (appender == null) {
213            return;
214        }
215        Filter filter = appender.getFilter();
216        while (filter != null && !(filter instanceof LevelRangeFilter)) {
217            filter = filter.getNext();
218        }
219        if (filter != null) {
220            LevelRangeFilter levelRangeFilter = (LevelRangeFilter) filter;
221            levelRangeFilter.setLevelMin(Level.WARN);
222            log.debug("Log level filter set to WARN for appender " + appenderName);
223        }
224    }
225
226}