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