org.apache.log4j.PropertyConfigurator Maven / Gradle / Ivy
/*
* 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.
*/
// Contibutors: "Luke Blanshard"
// "Mark DONSZELMANN"
// Anders Kristensen
package org.apache.log4j;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Iterator;
import java.util.Map;
import org.apache.log4j.config.PropertySetter;
import org.apache.log4j.helpers.FileWatchdog;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.or.RendererMap;
import org.apache.log4j.spi.Configurator;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggerFactory;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.OptionHandler;
import org.apache.log4j.spi.RendererSupport;
import org.apache.log4j.spi.ThrowableRenderer;
import org.apache.log4j.spi.ThrowableRendererSupport;
import org.apache.log4j.spi.ErrorHandler;
/**
* Allows the configuration of log4j from an external file. See
* {@link #doConfigure(String, LoggerRepository)} for the expected
* format.
*
*
* It is sometimes useful to see how log4j is reading configuration files. You
* can enable log4j internal logging by defining the log4j.debug
* variable.
*
*
* As of log4j version 0.8.5, at class initialization time class, the file
* log4j.properties will be searched from the search path used to load
* classes. If the file can be found, then it will be fed to the
* {@link PropertyConfigurator#configure(java.net.URL)} method.
*
*
* The PropertyConfigurator
does not handle the advanced
* configuration features supported by the
* {@link org.apache.log4j.xml.DOMConfigurator DOMConfigurator} such as support
* custom {@link org.apache.log4j.spi.ErrorHandler ErrorHandlers}, nested
* appenders such as the {@link org.apache.log4j.AsyncAppender AsyncAppender},
* etc.
*
*
* All option values admit variable substitution. The syntax of
* variable substitution is similar to that of Unix shells. The string between
* an opening "${" and closing "}" is
* interpreted as a key. The value of the substituted variable can be defined as
* a system property or in the configuration file itself. The value of the key
* is first searched in the system properties, and if not found there, it is
* then searched in the configuration file being parsed. The corresponding value
* replaces the ${variableName} sequence. For example, if java.home
* system property is set to /home/xyz
, then every occurrence of
* the sequence ${java.home}
will be interpreted as
* /home/xyz
.
*
*
* @author Ceki Gülcü
* @author Anders Kristensen
* @since 0.8.1
*/
public class PropertyConfigurator implements Configurator {
/**
* Used internally to keep track of configured appenders.
*/
protected Hashtable registry = new Hashtable(11);
private LoggerRepository repository;
protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
static final String CATEGORY_PREFIX = "log4j.category.";
static final String LOGGER_PREFIX = "log4j.logger.";
static final String FACTORY_PREFIX = "log4j.factory";
static final String ADDITIVITY_PREFIX = "log4j.additivity.";
static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
static final String APPENDER_PREFIX = "log4j.appender.";
static final String RENDERER_PREFIX = "log4j.renderer.";
static final String THRESHOLD_PREFIX = "log4j.threshold";
private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
private static final String LOGGER_REF = "logger-ref";
private static final String ROOT_REF = "root-ref";
private static final String APPENDER_REF_TAG = "appender-ref";
/**
* Key for specifying the {@link org.apache.log4j.spi.LoggerFactory
* LoggerFactory}. Currently set to "log4j.loggerFactory
".
*/
public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
/**
* If property set to true, then hierarchy will be reset before configuration.
*/
private static final String RESET_KEY = "log4j.reset";
static final private String INTERNAL_ROOT_NAME = "root";
/**
* Read configuration from a file. The existing configuration is not cleared
* nor reset. If you require a different behavior, then call
* {@link LogManager#resetConfiguration resetConfiguration} method before
* calling doConfigure
.
*
*
* The configuration file consists of statements in the format
* key=value
. The syntax of different configuration elements are
* discussed below.
*
*
Repository-wide threshold
*
*
* The repository-wide threshold filters logging requests by level regardless of
* logger. The syntax is:
*
*
log4j.threshold=[level]
*
*
*
* The level value can consist of the string values OFF, FATAL, ERROR, WARN,
* INFO, DEBUG, ALL or a custom level value. A custom level value can
* be specified in the form level#classname. By default the repository-wide
* threshold is set to the lowest possible value, namely the level
* ALL
.
*
*
*
* Appender configuration
*
*
* Appender configuration syntax is:
*
*
# For appender named appenderName, set its class.
# Note: The appender name can contain dots.
log4j.appender.appenderName=fully.qualified.name.of.appender.class
# Set appender specific options.
log4j.appender.appenderName.option1=value1
...
log4j.appender.appenderName.optionN=valueN
*
*
* For each named appender you can configure its {@link Layout}. The syntax for
* configuring an appender's layout is:
*
*
log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
log4j.appender.appenderName.layout.option1=value1
....
log4j.appender.appenderName.layout.optionN=valueN
*
*
* The syntax for adding {@link Filter}s to an appender is:
*
*
log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
log4j.appender.appenderName.filter.ID.option1=value1
...
log4j.appender.appenderName.filter.ID.optionN=valueN
*
*
* The first line defines the class name of the filter identified by ID;
* subsequent lines with the same ID specify filter option - value paris.
* Multiple filters are added to the appender in the lexicographic order of IDs.
*
* The syntax for adding an {@link ErrorHandler} to an appender is:
*
*
log4j.appender.appenderName.errorhandler=fully.qualified.name.of.filter.class
log4j.appender.appenderName.errorhandler.root-ref={true|false}
log4j.appender.appenderName.errorhandler.logger-ref=loggerName
log4j.appender.appenderName.errorhandler.appender-ref=appenderName
log4j.appender.appenderName.errorhandler.option1=value1
...
log4j.appender.appenderName.errorhandler.optionN=valueN
*
*
* Configuring loggers
*
*
* The syntax for configuring the root logger is:
*
*
log4j.rootLogger=[level], appenderName, appenderName, ...
*
*
*
* This syntax means that an optional level can be supplied followed by
* appender names separated by commas.
*
*
* The level value can consist of the string values OFF, FATAL, ERROR, WARN,
* INFO, DEBUG, ALL or a custom level value. A custom level value can
* be specified in the form level#classname
.
*
*
* If a level value is specified, then the root level is set to the
* corresponding level. If no level value is specified, then the root level
* remains untouched.
*
*
* The root logger can be assigned multiple appenders.
*
*
* Each appenderName (separated by commas) will be added to the root
* logger. The named appender is defined using the appender syntax defined
* above.
*
*
* For non-root categories the syntax is almost the same:
*
*
log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
*
*
*
* The meaning of the optional level value is discussed above in relation to the
* root logger. In addition however, the value INHERITED can be specified
* meaning that the named logger should inherit its level from the logger
* hierarchy.
*
*
* If no level value is supplied, then the level of the named logger remains
* untouched.
*
*
* By default categories inherit their level from the hierarchy. However, if you
* set the level of a logger and later decide that that logger should inherit
* its level, then you should specify INHERITED as the value for the level
* value. NULL is a synonym for INHERITED.
*
*
* Similar to the root logger syntax, each appenderName (separated by
* commas) will be attached to the named logger.
*
*
* See the appender additivity
* rule in the user manual for the meaning of the additivity
* flag.
*
*
ObjectRenderers
*
* You can customize the way message objects of a given type are converted to
* String before being logged. This is done by specifying an
* {@link org.apache.log4j.or.ObjectRenderer ObjectRenderer} for the object type
* would like to customize.
*
*
* The syntax is:
*
*
* log4j.renderer.fully.qualified.name.of.rendered.class = fully.qualified.name.of.rendering.class
*
*
* As in,
*
*
* log4j.renderer.my.Fruit = my.FruitRenderer
*
*
* ThrowableRenderer
*
* You can customize the way an instance of Throwable is converted to String
* before being logged. This is done by specifying an
* {@link org.apache.log4j.spi.ThrowableRenderer ThrowableRenderer}.
*
*
* The syntax is:
*
*
log4j.throwableRenderer=fully.qualified.name.of.rendering.class
log4j.throwableRenderer.paramName=paramValue
*
*
* As in,
*
*
* log4j.throwableRenderer = org.apache.log4j.EnhancedThrowableRenderer
*
*
* Logger Factories
*
* The usage of custom logger factories is discouraged and no longer documented.
*
* Resetting Hierarchy
*
* The hierarchy will be reset before configuration when log4j.reset=true is
* present in the properties file.
*
* Example
*
*
* An example configuration is given below. Other configuration file examples
* are given in the examples
folder.
*
*
# Set options for appender named "A1".
# Appender "A1" will be a SyslogAppender
log4j.appender.A1=org.apache.log4j.net.SyslogAppender
# The syslog daemon resides on www.abc.net
log4j.appender.A1.SyslogHost=www.abc.net
# A1's layout is a PatternLayout, using the conversion pattern
# %r %-5p %c{2} %M.%L %x - %m\n. Thus, the log output will
# include # the relative time since the start of the application in
# milliseconds, followed by the level of the log request,
# followed by the two rightmost components of the logger name,
# followed by the callers method name, followed by the line number,
# the nested disgnostic context and finally the message itself.
# Refer to the documentation of {@link PatternLayout} for further information
# on the syntax of the ConversionPattern key.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
# Set options for appender named "A2"
# A2 should be a RollingFileAppender, with maximum file size of 10 MB
# using at most one backup file. A2's layout is TTCC, using the
# ISO8061 date format with context printing enabled.
log4j.appender.A2=org.apache.log4j.RollingFileAppender
log4j.appender.A2.MaxFileSize=10MB
log4j.appender.A2.MaxBackupIndex=1
log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
log4j.appender.A2.layout.ContextPrinting=enabled
log4j.appender.A2.layout.DateFormat=ISO8601
# Root logger set to DEBUG using the A2 appender defined above.
log4j.rootLogger=DEBUG, A2
# Logger definitions:
# The SECURITY logger inherits is level from root. However, it's output
# will go to A1 appender defined above. It's additivity is non-cumulative.
log4j.logger.SECURITY=INHERIT, A1
log4j.additivity.SECURITY=false
# Only warnings or above will be logged for the logger "SECURITY.access".
# Output will go to A1.
log4j.logger.SECURITY.access=WARN
# The logger "class.of.the.day" inherits its level from the
# logger hierarchy. Output will go to the appender's of the root
# logger, A2 in this case.
log4j.logger.class.of.the.day=INHERIT
*
*
*
* Refer to the setOption method in each Appender and Layout for class
* specific options.
*
*
* Use the #
or !
characters at the beginning of a
* line for comments.
*
*
*
* @param configFileName The name of the configuration file where the
* configuration information is stored.
*
*/
public void doConfigure(String configFileName, LoggerRepository hierarchy) {
Properties props = new Properties();
FileInputStream istream = null;
try {
istream = new FileInputStream(configFileName);
props.load(istream);
istream.close();
} catch (Exception e) {
if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not read configuration file [" + configFileName + "].", e);
LogLog.error("Ignoring configuration file [" + configFileName + "].");
return;
} finally {
if (istream != null) {
try {
istream.close();
} catch (InterruptedIOException ignore) {
Thread.currentThread().interrupt();
} catch (Throwable ignore) {
}
}
}
// If we reach here, then the config file is alright.
doConfigure(props, hierarchy);
}
/**
*/
static public void configure(String configFilename) {
new PropertyConfigurator().doConfigure(configFilename, LogManager.getLoggerRepository());
}
/**
* Read configuration options from url configURL
.
*
* @since 0.8.2
*/
public static void configure(java.net.URL configURL) {
new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository());
}
/**
* Reads configuration options from an InputStream.
*
* @since 1.2.17
*/
public static void configure(InputStream inputStream) {
new PropertyConfigurator().doConfigure(inputStream, LogManager.getLoggerRepository());
}
/**
* Read configuration options from properties
.
*
* See {@link #doConfigure(String, LoggerRepository)} for the expected format.
*/
static public void configure(Properties properties) {
new PropertyConfigurator().doConfigure(properties, LogManager.getLoggerRepository());
}
/**
* Like {@link #configureAndWatch(String, long)} except that the default delay
* as defined by {@link FileWatchdog#DEFAULT_DELAY} is used.
*
* @param configFilename A file in key=value format.
*
*/
static public void configureAndWatch(String configFilename) {
configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
}
/**
* Read the configuration file configFilename
if it exists.
* Moreover, a thread will be created that will periodically check if
* configFilename
has been created or modified. The period is
* determined by the delay
argument. If a change or file creation
* is detected, then configFilename
is read to configure log4j.
*
* @param configFilename A file in key=value format.
* @param delay The delay in milliseconds to wait between each check.
*/
static public void configureAndWatch(String configFilename, long delay) {
PropertyWatchdog pdog = new PropertyWatchdog(configFilename);
pdog.setDelay(delay);
pdog.start();
}
/**
* Read configuration options from properties
.
*
* See {@link #doConfigure(String, LoggerRepository)} for the expected format.
*/
public void doConfigure(Properties properties, LoggerRepository hierarchy) {
repository = hierarchy;
String value = properties.getProperty(LogLog.DEBUG_KEY);
if (value == null) {
value = properties.getProperty("log4j.configDebug");
if (value != null)
LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
}
if (value != null) {
LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
}
//
// if log4j.reset=true then
// reset hierarchy
String reset = properties.getProperty(RESET_KEY);
if (reset != null && OptionConverter.toBoolean(reset, false)) {
hierarchy.resetConfiguration();
}
String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);
if (thresholdStr != null) {
hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL));
LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "].");
}
configureRootCategory(properties, hierarchy);
configureLoggerFactory(properties);
parseCatsAndRenderers(properties, hierarchy);
LogLog.debug("Finished configuring.");
// We don't want to hold references to appenders preventing their
// garbage collection.
registry.clear();
}
/**
* Read configuration options from url configURL
.
*
* @since 1.2.17
*/
public void doConfigure(InputStream inputStream, LoggerRepository hierarchy) {
Properties props = new Properties();
try {
props.load(inputStream);
} catch (IOException e) {
if (e instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not read configuration file from InputStream [" + inputStream + "].", e);
LogLog.error("Ignoring configuration InputStream [" + inputStream + "].");
return;
}
this.doConfigure(props, hierarchy);
}
/**
* Read configuration options from url configURL
.
*/
public void doConfigure(java.net.URL configURL, LoggerRepository hierarchy) {
Properties props = new Properties();
LogLog.debug("Reading configuration from URL " + configURL);
InputStream istream = null;
URLConnection uConn = null;
try {
uConn = configURL.openConnection();
uConn.setUseCaches(false);
istream = uConn.getInputStream();
props.load(istream);
} catch (Exception e) {
if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
LogLog.error("Could not read configuration file from URL [" + configURL + "].", e);
LogLog.error("Ignoring configuration file [" + configURL + "].");
return;
} finally {
if (istream != null) {
try {
istream.close();
} catch (InterruptedIOException ignore) {
Thread.currentThread().interrupt();
} catch (IOException ignore) {
} catch (RuntimeException ignore) {
}
}
}
doConfigure(props, hierarchy);
}
// --------------------------------------------------------------------------
// Internal stuff
// --------------------------------------------------------------------------
/**
* Check the provided Properties
object for a
* {@link org.apache.log4j.spi.LoggerFactory LoggerFactory} entry specified by
* {@link #LOGGER_FACTORY_KEY}. If such an entry exists, an attempt is made to
* create an instance using the default constructor. This instance is used for
* subsequent Category creations within this configurator.
*
* @see #parseCatsAndRenderers
*/
protected void configureLoggerFactory(Properties props) {
String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, props);
if (factoryClassName != null) {
LogLog.debug("Setting category factory to [" + factoryClassName + "].");
loggerFactory = (LoggerFactory) OptionConverter.instantiateByClassName(factoryClassName,
LoggerFactory.class, loggerFactory);
PropertySetter.setProperties(loggerFactory, props, FACTORY_PREFIX + ".");
}
}
/*
* void configureOptionHandler(OptionHandler oh, String prefix, Properties
* props) { String[] options = oh.getOptionStrings(); if(options == null)
* return;
*
* String value; for(int i = 0; i < options.length; i++) { value =
* OptionConverter.findAndSubst(prefix + options[i], props); LogLog.debug(
* "Option " + options[i] + "=[" + (value == null? "N/A" : value)+"]."); // Some
* option handlers assume that null value are not passed to them. // So don't
* remove this check if(value != null) { oh.setOption(options[i], value); } }
* oh.activateOptions(); }
*/
void configureRootCategory(Properties props, LoggerRepository hierarchy) {
String effectiveFrefix = ROOT_LOGGER_PREFIX;
String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, props);
if (value == null) {
value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, props);
effectiveFrefix = ROOT_CATEGORY_PREFIX;
}
if (value == null)
LogLog.debug("Could not find root logger information. Is this OK?");
else {
Logger root = hierarchy.getRootLogger();
synchronized (root) {
parseCategory(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
}
}
}
/**
* Parse non-root elements, such non-root categories and renderers.
*/
protected void parseCatsAndRenderers(Properties props, LoggerRepository hierarchy) {
Enumeration enumeration = props.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
String loggerName = null;
if (key.startsWith(CATEGORY_PREFIX)) {
loggerName = key.substring(CATEGORY_PREFIX.length());
} else if (key.startsWith(LOGGER_PREFIX)) {
loggerName = key.substring(LOGGER_PREFIX.length());
}
String value = OptionConverter.findAndSubst(key, props);
Logger logger = hierarchy.getLogger(loggerName, loggerFactory);
synchronized (logger) {
parseCategory(props, logger, key, loggerName, value);
parseAdditivityForLogger(props, logger, loggerName);
}
} else if (key.startsWith(RENDERER_PREFIX)) {
String renderedClass = key.substring(RENDERER_PREFIX.length());
String renderingClass = OptionConverter.findAndSubst(key, props);
if (hierarchy instanceof RendererSupport) {
RendererMap.addRenderer((RendererSupport) hierarchy, renderedClass, renderingClass);
}
} else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
if (hierarchy instanceof ThrowableRendererSupport) {
ThrowableRenderer tr = (ThrowableRenderer) OptionConverter.instantiateByKey(props,
THROWABLE_RENDERER_PREFIX, org.apache.log4j.spi.ThrowableRenderer.class, null);
if (tr == null) {
LogLog.error("Could not instantiate throwableRenderer.");
} else {
PropertySetter setter = new PropertySetter(tr);
setter.setProperties(props, THROWABLE_RENDERER_PREFIX + ".");
((ThrowableRendererSupport) hierarchy).setThrowableRenderer(tr);
}
}
}
}
}
/**
* Parse the additivity option for a non-root category.
*/
void parseAdditivityForLogger(Properties props, Logger cat, String loggerName) {
String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, props);
LogLog.debug("Handling " + ADDITIVITY_PREFIX + loggerName + "=[" + value + "]");
// touch additivity only if necessary
if ((value != null) && (!value.equals(""))) {
boolean additivity = OptionConverter.toBoolean(value, true);
LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity);
cat.setAdditivity(additivity);
}
}
/**
* This method must work for the root category as well.
*/
void parseCategory(Properties props, Logger logger, String optionKey, String loggerName, String value) {
LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "].");
// We must skip over ',' but not white space
StringTokenizer st = new StringTokenizer(value, ",");
// If value is not in the form ", appender.." or "", then we should set
// the level of the loggeregory.
if (!(value.startsWith(",") || value.equals(""))) {
// just to be on the safe side...
if (!st.hasMoreTokens())
return;
String levelStr = st.nextToken();
LogLog.debug("Level token is [" + levelStr + "].");
// If the level value is inherited, set category level value to
// null. We also check that the user has not specified inherited for the
// root category.
if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
if (loggerName.equals(INTERNAL_ROOT_NAME)) {
LogLog.warn("The root logger cannot be set to null.");
} else {
logger.setLevel(null);
}
} else {
logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
}
LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
}
// Begin by removing all existing appenders.
logger.removeAllAppenders();
Appender appender;
String appenderName;
while (st.hasMoreTokens()) {
appenderName = st.nextToken().trim();
if (appenderName == null || appenderName.equals(","))
continue;
LogLog.debug("Parsing appender named \"" + appenderName + "\".");
appender = parseAppender(props, appenderName);
if (appender != null) {
logger.addAppender(appender);
}
}
}
Appender parseAppender(Properties props, String appenderName) {
Appender appender = registryGet(appenderName);
if ((appender != null)) {
LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
return appender;
}
// Appender was not previously initialized.
String prefix = APPENDER_PREFIX + appenderName;
String layoutPrefix = prefix + ".layout";
appender = (Appender) OptionConverter.instantiateByKey(props, prefix, org.apache.log4j.Appender.class, null);
if (appender == null) {
LogLog.error("Could not instantiate appender named \"" + appenderName + "\".");
return null;
}
appender.setName(appenderName);
if (appender instanceof OptionHandler) {
if (appender.requiresLayout()) {
Layout layout = (Layout) OptionConverter.instantiateByKey(props, layoutPrefix, Layout.class, null);
if (layout != null) {
appender.setLayout(layout);
LogLog.debug("Parsing layout options for \"" + appenderName + "\".");
// configureOptionHandler(layout, layoutPrefix + ".", props);
PropertySetter.setProperties(layout, props, layoutPrefix + ".");
LogLog.debug("End of parsing for \"" + appenderName + "\".");
}
}
final String errorHandlerPrefix = prefix + ".errorhandler";
String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
if (errorHandlerClass != null) {
ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(props, errorHandlerPrefix,
ErrorHandler.class, null);
if (eh != null) {
appender.setErrorHandler(eh);
LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\".");
parseErrorHandler(eh, errorHandlerPrefix, props, repository);
final Properties edited = new Properties();
final String[] keys = new String[] { errorHandlerPrefix + "." + ROOT_REF,
errorHandlerPrefix + "." + LOGGER_REF, errorHandlerPrefix + "." + APPENDER_REF_TAG };
for (Iterator iter = props.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
int i = 0;
for (; i < keys.length; i++) {
if (keys[i].equals(entry.getKey()))
break;
}
if (i == keys.length) {
edited.put(entry.getKey(), entry.getValue());
}
}
PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\".");
}
}
// configureOptionHandler((OptionHandler) appender, prefix + ".", props);
PropertySetter.setProperties(appender, props, prefix + ".");
LogLog.debug("Parsed \"" + appenderName + "\" options.");
}
parseAppenderFilters(props, appenderName, appender);
registryPut(appender);
return appender;
}
private void parseErrorHandler(final ErrorHandler eh, final String errorHandlerPrefix, final Properties props,
final LoggerRepository hierarchy) {
boolean rootRef = OptionConverter.toBoolean(OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props),
false);
if (rootRef) {
eh.setLogger(hierarchy.getRootLogger());
}
String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF, props);
if (loggerName != null) {
Logger logger = (loggerFactory == null) ? hierarchy.getLogger(loggerName)
: hierarchy.getLogger(loggerName, loggerFactory);
eh.setLogger(logger);
}
String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
if (appenderName != null) {
Appender backup = parseAppender(props, appenderName);
if (backup != null) {
eh.setBackupAppender(backup);
}
}
}
void parseAppenderFilters(Properties props, String appenderName, Appender appender) {
// extract filters and filter options from props into a hashtable mapping
// the property name defining the filter class to a list of pre-parsed
// name-value pairs associated to that filter
final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
int fIdx = filterPrefix.length();
Hashtable filters = new Hashtable();
Enumeration e = props.keys();
String name = "";
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
if (key.startsWith(filterPrefix)) {
int dotIdx = key.indexOf('.', fIdx);
String filterKey = key;
if (dotIdx != -1) {
filterKey = key.substring(0, dotIdx);
name = key.substring(dotIdx + 1);
}
Vector filterOpts = (Vector) filters.get(filterKey);
if (filterOpts == null) {
filterOpts = new Vector();
filters.put(filterKey, filterOpts);
}
if (dotIdx != -1) {
String value = OptionConverter.findAndSubst(key, props);
filterOpts.add(new NameValue(name, value));
}
}
}
// sort filters by IDs, insantiate filters, set filter options,
// add filters to the appender
Enumeration g = new SortedKeyEnumeration(filters);
while (g.hasMoreElements()) {
String key = (String) g.nextElement();
String clazz = props.getProperty(key);
if (clazz != null) {
LogLog.debug(
"Filter key: [" + key + "] class: [" + props.getProperty(key) + "] props: " + filters.get(key));
Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
if (filter != null) {
PropertySetter propSetter = new PropertySetter(filter);
Vector v = (Vector) filters.get(key);
Enumeration filterProps = v.elements();
while (filterProps.hasMoreElements()) {
NameValue kv = (NameValue) filterProps.nextElement();
propSetter.setProperty(kv.key, kv.value);
}
propSetter.activate();
LogLog.debug("Adding filter of type [" + filter.getClass() + "] to appender named ["
+ appender.getName() + "].");
appender.addFilter(filter);
}
} else {
LogLog.warn("Missing class definition for filter: [" + key + "]");
}
}
}
void registryPut(Appender appender) {
registry.put(appender.getName(), appender);
}
Appender registryGet(String name) {
return (Appender) registry.get(name);
}
}
class PropertyWatchdog extends FileWatchdog {
PropertyWatchdog(String filename) {
super(filename);
}
/**
* Call {@link PropertyConfigurator#configure(String)} with the
* filename
to reconfigure log4j.
*/
public void doOnChange() {
new PropertyConfigurator().doConfigure(filename, LogManager.getLoggerRepository());
}
}
class NameValue {
String key, value;
public NameValue(String key, String value) {
this.key = key;
this.value = value;
}
public String toString() {
return key + "=" + value;
}
}
class SortedKeyEnumeration implements Enumeration {
private Enumeration e;
public SortedKeyEnumeration(Hashtable ht) {
Enumeration f = ht.keys();
Vector keys = new Vector(ht.size());
for (int i, last = 0; f.hasMoreElements(); ++last) {
String key = (String) f.nextElement();
for (i = 0; i < last; ++i) {
String s = (String) keys.get(i);
if (key.compareTo(s) <= 0)
break;
}
keys.add(i, key);
}
e = keys.elements();
}
public boolean hasMoreElements() {
return e.hasMoreElements();
}
public Object nextElement() {
return e.nextElement();
}
}