Package org.apache.sling.commons.log.slf4j

Source Code of org.apache.sling.commons.log.slf4j.LogConfigManager

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.commons.log.slf4j;

import java.io.File;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.apache.sling.commons.log.LogManager;
import org.osgi.service.cm.ConfigurationException;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;

public class LogConfigManager implements ILoggerFactory {

    public static final String ROOT = "";

    // the singleton instance of this class
    private static LogConfigManager instance = new LogConfigManager();

    // map of log writers indexed by configuration PID
    private final Map<String, SlingLoggerWriter> writerByPid;

    // map of log writers indexed by (absolute) file name. This map does
    // not contain writers writing to standard out
    private final Map<String, SlingLoggerWriter> writerByFileName;

    // map of log configurations by configuration PID
    private final Map<String, SlingLoggerConfig> configByPid;

    // map of log configurations by the categories they are configured with
    private final Map<String, SlingLoggerConfig> configByCategory;

    // map of all loggers supplied by getLogger(String) by their names. Each
    // entry is in fact a SoftReference to the actual logger, such that the
    // loggers may be cleaned up if no used any more.
    // There is no ReferenceQueue handling currently for removed loggers
    private final Map<String, SoftReference<SlingLogger>> loggersByCategory;

    // the logger used by this instance
    private final Logger log;

    // the default logger configuration set up by the constructor and managed
    // by the global logger configuration
    private final SlingLoggerConfig defaultLoggerConfig;

    // the default writer configuration set up by the constructor and managed
    // by the global logger configuration
    private final SlingLoggerWriter defaultWriter;

    // the root folder to make relative writer paths absolute
    private File rootDir;

    /**
     * Returns the single instance of this log configuration instance.
     */
    public static LogConfigManager getInstance() {
        return instance;
    }

    /**
     * Logs a message an optional stack trace to error output. This method is
     * used by the logging system in case of errors writing to the correct
     * logging output.
     */
    public static void internalFailure(String message, Throwable t) {
        System.err.println(message);
        if (t != null) {
            t.printStackTrace(System.err);
        }
    }

    /**
     * Sets up this log configuration manager by creating the default writers
     * and logger configuration
     */
    private LogConfigManager() {
        writerByPid = new HashMap<String, SlingLoggerWriter>();
        writerByFileName = new HashMap<String, SlingLoggerWriter>();
        configByPid = new HashMap<String, SlingLoggerConfig>();
        configByCategory = new HashMap<String, SlingLoggerConfig>();
        loggersByCategory = new HashMap<String, SoftReference<SlingLogger>>();

        // configure the default writer to write to stdout (for now)
        // and register for PID only
        defaultWriter = new SlingLoggerWriter(LogManager.PID);
        try {
            defaultWriter.configure(null, 0, "0");
        } catch (IOException ioe) {
            internalFailure("Cannot initialize default SlingLoggerWriter", ioe);
        }
        writerByPid.put(LogManager.PID, defaultWriter);

        // set up the default configuration using the default logger
        // writing at INFO level to start with
        Set<String> defaultCategories = new HashSet<String>();
        defaultCategories.add(ROOT);
        defaultLoggerConfig = new SlingLoggerConfig(LogManager.PID,
            LogManager.LOG_PATTERN_DEFAULT, defaultCategories,
            SlingLoggerLevel.INFO, defaultWriter);
        configByPid.put(LogManager.PID, defaultLoggerConfig);
        configByCategory.put(ROOT, defaultLoggerConfig);

        // get me my logger
        log = getLogger(getClass().getName());
    }

    /**
     * Sets the root (folder) to be used to make relative paths absolute.
     */
    public void setRoot(String root) {
        rootDir = new File((root == null) ? "" : root).getAbsoluteFile();
    }

    /**
     * Shuts this configuration manager down by dropping all references to
     * existing configurations, dropping all stored loggers and closing all log
     * writers.
     * <p>
     * After this methods is called, this instance should not be used again.
     */
    public void close() {
        writerByPid.clear();
        writerByFileName.clear();
        configByPid.clear();
        configByCategory.clear();

        // remove references to the loggers
        for (SoftReference<SlingLogger> logger : loggersByCategory.values()) {
            logger.clear();
        }
        loggersByCategory.clear();

        // shutdown the default writer
        try {
            defaultWriter.close();
        } catch (IOException ignore) {
            // don't care for this
        }
    }

    // ---------- ILoggerFactory -----------------------------------------------

    /**
     * Returns the name logger. If no logger for the name already exists, it is
     * created and configured on the fly and returned. If a logger of the same
     * name already exists, that logger is returned.
     */
    public Logger getLogger(String name) {
        SoftReference<SlingLogger> logger = loggersByCategory.get(name);
        SlingLogger slingLogger = (logger != null) ? logger.get() : null;

        // no logger at all or reference has been collected, create a new one
        if (slingLogger == null) {
            slingLogger = new SlingLogger(name);
            slingLogger.setLoggerConfig(getLoggerConfig(name));
            loggersByCategory.put(name, new SoftReference<SlingLogger>(
                slingLogger));
        }

        return slingLogger;
    }

    // ---------- Configuration support ----------------------------------------

    /**
     * Updates or removes the log writer configuration identified by the
     * <code>pid</code>. In case of log writer removal, any logger
     * configuration referring to the removed log writer is modified to now log
     * to the default log writer.
     * <p>
     * The configuration object is expected to contain the following properties:
     * <dl>
     * <dt>{@link LogManager#LOG_FILE}</dt>
     * <dd>The relative of absolute path/name of the file to log to. If this
     * property is missing or an empty string, the writer writes to standard
     * output</dd>
     * <dt>{@link LogManager#LOG_FILE_SIZE}</dt>
     * <dd>The maximum size of the log file to write before rotating the log
     * file. This property must be a number of be convertible to a number. The
     * actual value may also be suffixed by a size indicator <code>k</code>,
     * <code>kb</code>, <code>m</code>, <code>mb</code>, <code>g</code>
     * or <code>gb</code> representing the respective factors of kilo, mega
     * and giga.If this property is missing or cannot be converted to a number,
     * the default value {@link LogManager#LOG_FILE_SIZE_DEFAULT} is assumed. If
     * the writer writes standard output this property is ignored.</dd>
     * <dt>{@link LogManager#LOG_FILE_NUMBER}</dt>
     * <dd>The maximum number of rotated log files to keep. This property must
     * be a number of be convertible to a number. If this property is missing or
     * cannot be converted to a number, the default value
     * {@link LogManager#LOG_FILE_NUMBER_DEFAULT} is assumed. If the writer
     * writes standard output this property is ignored.</dd>
     * </dl>
     *
     * @param pid The identifier of the log writer to update or remove
     * @param configuration New configuration setting for the log writer or
     *            <code>null</code> to indicate to remove the log writer.
     * @throws ConfigurationException If another log writer already exists for
     *             the same file as configured for the given log writer or if
     *             configuring the log writer fails.
     */
    public void updateLogWriter(String pid, Dictionary<?, ?> configuration)
            throws ConfigurationException {

        if (configuration != null) {
            SlingLoggerWriter slw = writerByPid.get(pid);

            // get the log file parameter and normalize empty string to null
            String logFileName = (String) configuration.get(LogManager.LOG_FILE);
            if (logFileName != null && logFileName.trim().length() == 0) {
                logFileName = null;
            }

            // if we have a file name, make it absolute and correct for our
            // environment and verify there is no other writer already existing
            // for the same file
            if (logFileName != null) {

                // ensure absolute path
                logFileName = getAbsoluteLogFile(logFileName);

                // ensure unique configuration of the log writer
                SlingLoggerWriter existingWriter = writerByFileName.get(logFileName);
                if (existingWriter != null
                    && existingWriter.getConfigurationPID().equals(pid)) {
                    // this file is already configured by another LOG_PID
                    throw new ConfigurationException(LogManager.LOG_FILE,
                        "LogFile " + logFileName
                            + " already configured by configuration "
                            + existingWriter.getConfigurationPID());
                }
            }

            // get number of files and ensure minimum and default
            Object fileNumProp = configuration.get(LogManager.LOG_FILE_NUMBER);
            int fileNum = -1;
            if (fileNumProp instanceof Number) {
                fileNum = ((Number) fileNumProp).intValue();
            } else if (fileNumProp != null) {
                try {
                    fileNum = Integer.parseInt(fileNumProp.toString());
                } catch (NumberFormatException nfe) {
                    // don't care
                }
            }
            if (fileNum <= 0) {
                fileNum = LogManager.LOG_FILE_NUMBER_DEFAULT;
            }

            // get the log file size
            Object fileSizeProp = configuration.get(LogManager.LOG_FILE_SIZE);
            String fileSize = null;
            if (fileSizeProp != null) {
                fileSize = fileSizeProp.toString();
            }
            if (fileSize == null || fileSize.length() == 0) {
                fileSize = LogManager.LOG_FILE_SIZE_DEFAULT;
            }

            try {
                if (slw == null) {
                    slw = new SlingLoggerWriter(pid);
                    slw.configure(logFileName, fileNum, fileSize);
                    writerByPid.put(pid, slw);

                    if (logFileName != null) {
                        writerByFileName.put(logFileName, slw);
                    }
                } else {
                    slw.configure(logFileName, fileNum, fileSize);
                }
            } catch (IOException ioe) {
                internalFailure("Cannot create log file " + logFileName, ioe);
                internalFailure("Logging to the console", null);
                throw new ConfigurationException(LogManager.LOG_FILE,
                    "Cannot create writer for log file " + logFileName);
            }

        } else {

            SlingLoggerWriter logWriter = writerByPid.remove(pid);
            if (logWriter != null) {

                // if the writer is writing to a file, remove the file mapping
                String path = logWriter.getPath();
                if (path != null) {
                    writerByFileName.remove(path);
                }

                // make sure, no configuration is referring to this writer
                // any more
                for (SlingLoggerConfig config : configByPid.values()) {
                    if (config.getLogWriter() == logWriter) {
                        log.info(
                            "updateLogWriter: Resetting configuration {} to the standard log writer",
                            config.getConfigPid());
                        config.setLogWriter(defaultWriter);
                    }
                }

                // close the removed log writer
                try {
                    logWriter.close();
                } catch (IOException ioe) {
                    // don't care
                }
            }
        }
    }

    /**
     * Updates or removes the logger configuration indicated by the given
     * <code>pid</code>. If the case of modified categories or removal of the
     * logger configuration, existing loggers will be modified to reflect the
     * correct logger configurations available.
     * <p>
     * The configuration object is expected to contain the following properties:
     * <dl>
     * <dt>{@link LogManager#LOG_PATTERN}</dt>
     * <dd>The <code>MessageFormat</code> pattern to apply to format the log
     * message before writing it to the log writer. If this property is missing
     * or the empty string the default pattern
     * {@link LogManager#LOG_PATTERN_DEFAULT} is used.</dd>
     * <dt>{@link LogManager#LOG_LEVEL}</dt>
     * <dd>The log level to use for log message limitation. The supported
     * values are <code>trace</code>, <code>debug</code>,
     * <code>info</code>, <code>warn</code> and <code>error</code>. Case
     * does not matter. If this property is missing a
     * <code>ConfigurationException</code> is thrown and this logger
     * configuration is not used.</dd>
     * <dt>{@link LogManager#LOG_LOGGERS}</dt>
     * <dd>The logger names to which this configuration applies. As logger
     * names form a hierarchy like Java packages, the listed names also apply to
     * "child names" unless more specific configuration applies for such
     * children. This property may be a single string, an array of strings or a
     * vector of strings. Each string may itself be a comma-separated list of
     * logger names. If this property is missing a
     * <code>ConfigurationException</code> is thrown.</dd>
     * <dt>{@link LogManager#LOG_FILE}</dt>
     * <dd>The name of the log writer to use. This may be the name of a log
     * file configured for any log writer or it may be the configuration PID of
     * such a writer. If this property is missing or empty or does not refer to
     * an existing log writer configuration, the default log writer is used.</dd>
     *
     * @param pid The name of the configuration to update or remove.
     * @param configuration The configuration object.
     * @throws ConfigurationException If the log level and logger names
     *             properties are not configured for the given configuration.
     */
    public void updateLoggerConfiguration(String pid,
            Dictionary<?, ?> configuration) throws ConfigurationException {

        // assume we have to reconfigure the loggers
        boolean reconfigureLoggers = true;

        if (configuration != null) {

            String pattern = (String) configuration.get(LogManager.LOG_PATTERN);
            String level = (String) configuration.get(LogManager.LOG_LEVEL);
            String file = (String) configuration.get(LogManager.LOG_FILE);
            Set<String> categories = toCategoryList(configuration.get(LogManager.LOG_LOGGERS));

            // verify categories
            if (categories == null) {
                throw new ConfigurationException(LogManager.LOG_LOGGERS,
                    "Missing categories in configuration " + pid);
            }

            // verify no other configuration has any of the categories
            for (String cat : categories) {
                SlingLoggerConfig cfg = configByCategory.get(cat);
                if (cfg != null && !pid.equals(cfg.getConfigPid())) {
                    throw new ConfigurationException(LogManager.LOG_LOGGERS,
                        "Category " + cat
                            + " already defined by configuration " + pid);
                }
            }

            // verify writer
            SlingLoggerWriter writer;
            if (file != null && file.length() > 0) {
                writer = writerByPid.get(file);
                if (writer == null) {
                    writer = writerByFileName.get(file);
                    if (writer == null) {
                        file = getAbsoluteLogFile(file);
                        writer = writerByFileName.get(file);
                        if (writer == null) {
                            writer = defaultWriter;
                        }
                    }
                }
            } else {
                writer = defaultWriter;
            }

            // verify log level
            if (level == null) {
                throw new ConfigurationException(LogManager.LOG_LEVEL,
                    "Value required");
            }
            SlingLoggerLevel logLevel = SlingLoggerLevel.valueOf(level.toUpperCase());

            // verify pattern
            if (pattern == null || pattern.length() == 0) {
                pattern = LogManager.LOG_PATTERN_DEFAULT;
            }

            // create or modify existing configuration object
            SlingLoggerConfig config = configByPid.get(pid);
            if (config == null) {

                // create and store new configuration
                config = new SlingLoggerConfig(pid, pattern, categories,
                    logLevel, writer);
                configByPid.put(pid, config);

            } else {

                // remove category to configuration mappings
                Set<String> oldCategories = config.getCategories();

                // reconfigure the configuration
                config.configure(pattern, categories, logLevel, writer);

                if (categories.equals(oldCategories)) {

                    // remove the old categories if different from the new ones
                    configByCategory.keySet().removeAll(oldCategories);

                } else {

                    // no need to change category registrations, clear them
                    // also no need to reconfigure the loggers
                    categories.clear();
                    reconfigureLoggers = false;

                }

            }

            // relink categories
            for (String cat : categories) {
                configByCategory.put(cat, config);
            }

        } else {

            // configuration deleted if null

            // remove configuration from pid list
            SlingLoggerConfig config = configByPid.remove(pid);

            // remove all configured categories
            if (config != null) {
                configByCategory.keySet().removeAll(config.getCategories());
            }

        }

        // reconfigure existing loggers
        if (reconfigureLoggers) {
            reconfigureLoggers();
        }
    }

    // ---------- Internal helpers ---------------------------------------------

    /**
     * Returns the <code>logFileName</code> argument converted into an
     * absolute path name. If <code>logFileName</code> is already absolute it
     * is returned unmodified. Otherwise it is made absolute by resolving it
     * relative to the root directory set on this instance by the
     * {@link #setRoot(String)} method.
     *
     * @throws NullPointerException if <code>logFileName</code> is
     *             <code>null</code>.
     */
    private String getAbsoluteLogFile(String logFileName) {
        // ensure proper separator in the path (esp. for systems, which do
        // not use "slash" as a separator, e.g Windows)
        logFileName = logFileName.replace('/', File.separatorChar);

        // create a file instance and check whether this is absolute. If not
        // create a new absolute file instance with the root dir and get
        // the absolute path name from that
        File logFile = new File(logFileName);
        if (!logFile.isAbsolute()) {
            logFile = new File(rootDir, logFileName);
            logFileName = logFile.getAbsolutePath();
        }

        // return the correct log file name
        return logFileName;
    }

    /**
     * Reconfigures all loggers such that each logger is supplied with the
     * {@link SlingLoggerConfig} most appropriate to its name. If a registered
     * logger is not used any more, it is removed from the list.
     */
    private void reconfigureLoggers() {
        // assign correct logger configs to all existing/known loggers
        for (Iterator<SoftReference<SlingLogger>> si = loggersByCategory.values().iterator(); si.hasNext();) {
            SlingLogger logger = si.next().get();
            if (logger != null) {
                logger.setLoggerConfig(getLoggerConfig(logger.getName()));
            } else {
                // if the logger has been GC-ed, remove the entry from the map
                si.remove();
            }
        }
    }

    /**
     * Returns a {@link SlingLoggerConfig} instance applicable to the given
     * <code>logger</code> name. This is the instance applicable to a longest
     * match log. If no such instance exists, the default logger configuration
     * is returned.
     */
    private SlingLoggerConfig getLoggerConfig(String logger) {
        for (;;) {
            SlingLoggerConfig config = configByCategory.get(logger);
            if (config != null) {
                return config;
            }

            if (logger.length() == 0) {
                break;
            }

            int dot = logger.lastIndexOf('.');
            if (dot < 0) {
                logger = ROOT;
            } else {
                logger = logger.substring(0, dot);
            }
        }

        return defaultLoggerConfig;
    }

    /**
     * Decomposes the <code>loggers</code> configuration object into a set of
     * logger names. The <code>loggers</code> object may be a single string,
     * an array of strings or a vector of strings. Each string may in turn be a
     * comma-separated list of strings. Each entry makes up an entry in the
     * resulting set.
     *
     * @param loggers The configuration object to be decomposed. If this is
     *            <code>null</code>, <code>null</code> is returned
     *            immediately
     * @return The set of logger names provided by the <code>loggers</code>
     *         object or <code>null</code> if the <code>loggers</code>
     *         object itself is <code>null</code>.
     */
    private Set<String> toCategoryList(Object loggers) {

        // quick exit if there is no configuration
        if (loggers == null) {
            return null;
        }

        // prepare set of names (used already in case loggers == ROOT)
        Set<String> loggerNames = new HashSet<String>();

        // in case of the special setting ROOT, return a set of just the
        // root logger name (SLING-529)
        if (loggers == ROOT) {
            loggerNames.add(ROOT);
            return loggerNames;
        }

        // convert the loggers object to an array
        Object[] loggersArray;
        if (loggers.getClass().isArray()) {
            loggersArray = (Object[]) loggers;
        } else if (loggers instanceof Vector) {
            loggersArray = ((Vector<?>) loggers).toArray();
        } else {
            loggersArray = new Object[] { loggers };
        }

        // conver the array of potentially comma-separated logger names
        // into the set of logger names
        for (Object loggerObject : loggersArray) {
            if (loggerObject != null) {
                String[] splitLoggers = loggerObject.toString().split(",");
                for (String logger : splitLoggers) {
                    logger = logger.trim();
                    if (logger.length() > 0) {
                        loggerNames.add(logger);
                    }
                }
            }
        }

        // return those names
        return loggerNames;
    }

}
TOP

Related Classes of org.apache.sling.commons.log.slf4j.LogConfigManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.