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;
- }
-
}