Author: fmeschbe
Date: Thu Jun 12 03:21:44 2008
New Revision: 667029

URL: http://svn.apache.org/viewvc?rev=667029&view=rev
Log:
SLING-525 Optimize reconfiguration of a logger configuration without changing
the set of logger name and support modification of logger configurations 
referring
to log writers being removed.

Modified:
    
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/LogConfigManager.java
    
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLogger.java
    
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLoggerConfig.java

Modified: 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/LogConfigManager.java
URL: 
http://svn.apache.org/viewvc/incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/LogConfigManager.java?rev=667029&r1=667028&r2=667029&view=diff
==============================================================================
--- 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/LogConfigManager.java
 (original)
+++ 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/LogConfigManager.java
 Thu Jun 12 03:21:44 2008
@@ -38,24 +38,45 @@
 
     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;
 
-    private SlingLogger defaultLogger;
+    // the logger used by this instance
+    private final Logger log;
 
-    private SlingLoggerWriter defaultWriter;
+    // 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;
     }
@@ -72,6 +93,10 @@
         }
     }
 
+    /**
+     * 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>();
@@ -79,41 +104,44 @@
         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);
-
-        SlingLoggerConfig defaultConfig = new SlingLoggerConfig(LogManager.PID,
+        defaultLoggerConfig = new SlingLoggerConfig(LogManager.PID,
             LogManager.LOG_PATTERN_DEFAULT, defaultCategories,
             SlingLoggerLevel.INFO, defaultWriter);
+        configByPid.put(LogManager.PID, defaultLoggerConfig);
+        configByCategory.put(ROOT, defaultLoggerConfig);
 
-        defaultLogger = new SlingLogger(ROOT);
-        defaultLogger.configure(defaultConfig);
-
-        // register the writer (PID only, without configuration, the
-        // writer logs to sdtout and has no file name)
-        writerByPid.put(LogManager.PID, defaultWriter);
-
-        // register the config
-        configByPid.put(LogManager.PID, defaultConfig);
-        configByCategory.put(ROOT, defaultConfig);
-
-        // register the logger with root category
-        loggersByCategory.put(ROOT, new SoftReference<SlingLogger>(
-            defaultLogger));
+        // 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) {
-        // the base for relative path names
         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();
@@ -126,16 +154,21 @@
         }
         loggersByCategory.clear();
 
+        // shutdown the default writer
         try {
             defaultWriter.close();
         } catch (IOException ignore) {
             // don't care for this
         }
-        // defaultLogger.getConfiguration().close();
     }
 
     // ---------- 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;
@@ -143,7 +176,7 @@
         // no logger at all or reference has been collected, create a new one
         if (slingLogger == null) {
             slingLogger = new SlingLogger(name);
-            slingLogger.configure(getLoggerConfig(name));
+            slingLogger.setLoggerConfig(getLoggerConfig(name));
             loggersByCategory.put(name, new SoftReference<SlingLogger>(
                 slingLogger));
         }
@@ -153,7 +186,43 @@
 
     // ---------- Configuration support 
----------------------------------------
 
-    public void updateLogWriter(String pid, Dictionary configuration)
+    /**
+     * 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>[EMAIL PROTECTED] 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>[EMAIL PROTECTED] 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 [EMAIL PROTECTED] LogManager#LOG_FILE_SIZE_DEFAULT} 
is assumed. If
+     * the writer writes standard output this property is ignored.</dd>
+     * <dt>[EMAIL PROTECTED] 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
+     * [EMAIL PROTECTED] 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) {
@@ -165,9 +234,10 @@
                 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 proper separator in the path
-                logFileName = logFileName.replace('/', File.separatorChar);
 
                 // ensure absolute path
                 logFileName = getAbsoluteLogFile(logFileName);
@@ -244,7 +314,9 @@
                 // any more
                 for (SlingLoggerConfig config : configByPid.values()) {
                     if (config.getLogWriter() == logWriter) {
-                        // TODO: log this !
+                        log.info(
+                            "updateLogWriter: Resetting configuration {} to 
the standard log writer",
+                            config.getConfigPid());
                         config.setLogWriter(defaultWriter);
                     }
                 }
@@ -259,8 +331,50 @@
         }
     }
 
-    public void updateLoggerConfiguration(String pid, Dictionary configuration)
-            throws ConfigurationException {
+    /**
+     * 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>[EMAIL PROTECTED] 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
+     * [EMAIL PROTECTED] LogManager#LOG_PATTERN_DEFAULT} is used.</dd>
+     * <dt>[EMAIL PROTECTED] 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>[EMAIL PROTECTED] 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>[EMAIL PROTECTED] 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) {
 
@@ -327,12 +441,25 @@
             } else {
 
                 // remove category to configuration mappings
-                for (Iterator<String> ci = config.getCategories(); 
ci.hasNext();) {
-                    configByCategory.remove(ci.next());
-                }
+                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
@@ -349,33 +476,58 @@
 
             // remove all configured categories
             if (config != null) {
-                // remove category to configuration mappings
-                for (Iterator<String> ci = config.getCategories(); 
ci.hasNext();) {
-                    configByCategory.remove(ci.next());
-                }
+                configByCategory.keySet().removeAll(config.getCategories());
             }
 
         }
 
         // reconfigure existing loggers
-        reconfigureLoggers();
+        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
+     * [EMAIL PROTECTED] #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;
     }
 
-    void reconfigureLoggers() {
+    /**
+     * Reconfigures all loggers such that each logger is supplied with the
+     * [EMAIL PROTECTED] 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.configure(getLoggerConfig(logger.getName()));
+                logger.setLoggerConfig(getLoggerConfig(logger.getName()));
             } else {
                 // if the logger has been GC-ed, remove the entry from the map
                 si.remove();
@@ -383,7 +535,13 @@
         }
     }
 
-    SlingLoggerConfig getLoggerConfig(String logger) {
+    /**
+     * Returns a [EMAIL PROTECTED] 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) {
@@ -402,50 +560,57 @@
             }
         }
 
-        return defaultLogger.getConfiguration();
+        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) {
-        Set<String> loggerList = new HashSet<String>();
-        if (loggers == null) {
 
+        // quick exit if there is no configuration
+        if (loggers == null) {
             return null;
+        }
 
-        } else if (loggers.getClass().isArray()) {
-
-            for (Object loggerObject : (Object[]) loggers) {
-                if (loggerObject != null) {
-                    splitLoggers(loggerList, loggerObject);
-                }
-            }
-
+        // convert the loggers object to an array
+        Object[] loggersArray;
+        if (loggers.getClass().isArray()) {
+            loggersArray = (Object[]) loggers;
         } else if (loggers instanceof Vector) {
-
-            for (Object loggerObject : (Vector) loggers) {
-                if (loggerObject != null) {
-                    splitLoggers(loggerList, loggerObject);
-                }
-            }
-
+            loggersArray = ((Vector<?>) loggers).toArray();
         } else {
-
-            splitLoggers(loggerList, loggers);
-
+            loggersArray = new Object[] { loggers };
         }
 
-        return loggerList;
-    }
-
-    private void splitLoggers(Set<String> loggerList, Object loggerObject) {
-        if (loggerObject != null) {
-            String loggers[] = loggerObject.toString().split(",");
-            for (String logger : loggers) {
-                logger = logger.trim();
-                if (logger.length() > 0) {
-                    loggerList.add(logger);
+        // conver the array of potentially comma-separated logger names
+        // into the set of logger names
+        Set<String> loggerNames = new HashSet<String>();
+        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;
     }
 
 }

Modified: 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLogger.java
URL: 
http://svn.apache.org/viewvc/incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLogger.java?rev=667029&r1=667028&r2=667029&view=diff
==============================================================================
--- 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLogger.java
 (original)
+++ 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLogger.java
 Thu Jun 12 03:21:44 2008
@@ -35,11 +35,7 @@
         this.name = name;
     }
 
-    /**
-     * Configures this logger with the given log level, output and message
-     * format. None of these is allowed to be <code>null</code>.
-     */
-    void configure(SlingLoggerConfig config) {
+    void setLoggerConfig(SlingLoggerConfig config) {
         this.config = config;
     }
 
@@ -86,10 +82,6 @@
         return name;
     }
 
-    SlingLoggerConfig getConfiguration() {
-        return  config;
-    }
-    
     public void trace(String msg) {
         if (isTraceEnabled()) {
             log(null, SlingLoggerLevel.TRACE, msg, null);

Modified: 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLoggerConfig.java
URL: 
http://svn.apache.org/viewvc/incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLoggerConfig.java?rev=667029&r1=667028&r2=667029&view=diff
==============================================================================
--- 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLoggerConfig.java
 (original)
+++ 
incubator/sling/trunk/commons/log/src/main/java/org/apache/sling/commons/log/slf4j/SlingLoggerConfig.java
 Thu Jun 12 03:21:44 2008
@@ -23,11 +23,15 @@
 import java.text.MessageFormat;
 import java.util.Date;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.Set;
 
 import org.slf4j.Marker;
 
+/**
+ * The <code>SlingLoggerConfig</code> conveys the logger configuration in
+ * terms of writer used and log level set. The respective instances of this
+ * class are also used for the actual message formatting and writing.
+ */
 class SlingLoggerConfig {
 
     private String configPid;
@@ -62,8 +66,8 @@
         return categories.contains(category);
     }
 
-    Iterator<String> getCategories() {
-        return categories.iterator();
+    Set<String> getCategories() {
+        return categories;
     }
 
     SlingLoggerWriter getLogWriter() {
@@ -74,6 +78,18 @@
         this.writer = writer;
     }
 
+    SlingLoggerLevel getLogLevel() {
+        return level;
+    }
+
+    boolean isLevel(SlingLoggerLevel reference) {
+        return level.compareTo(reference) <= 0;
+    }
+
+    void setLogLevel(SlingLoggerLevel level) {
+        this.level = level;
+    }
+
     void formatMessage(StringBuffer buffer, Marker marker, String name,
             SlingLoggerLevel level, String msg, String fqcn) {
         // create the formatted log line; use a local copy because the field
@@ -108,34 +124,4 @@
         }
     }
 
-    // ---------- Log Level support 
--------------------------------------------
-
-    void setLogLevel(String levelString) {
-        try {
-            // ensure upper case level name
-            levelString = levelString.toUpperCase();
-
-            // try to convert to a SlingLoggerLevel instance,
-            // throws if the string is invalid
-            SlingLoggerLevel level = SlingLoggerLevel.valueOf(levelString);
-
-            // finally set the level
-            this.setLogLevel(level);
-        } catch (Exception e) {
-            // TODO: warn("Cannot set loglevel to " + level, e);
-        }
-    }
-
-    void setLogLevel(SlingLoggerLevel level) {
-        this.level = level;
-    }
-
-    SlingLoggerLevel getLogLevel() {
-        return level;
-    }
-
-    boolean isLevel(SlingLoggerLevel reference) {
-        return level.compareTo(reference) <= 0;
-    }
-
 }


Reply via email to